Showing posts with label LINQ. Show all posts
Showing posts with label LINQ. Show all posts

Jun 21, 2016

To infinity and beyond : more powerful .NET Metadata querying with LINQPad custom drivers and Mono.Cecil

In my last post I discussed how we can use LINQPad to query .NET metadata using .NET reflection. 

Even though that alone was already quite useful it has its own drawbacks:
  • No nice integration with LINQPad, requiring the user to load the assembly manually (even worse, forcing users to type the full assembly path)
  • No way to inspect / dump method bodies (so it is not possible, for instance, to find all methods that call into an specific method)
In order to overcome those limitations I decided to write a custom LINQPad driver for .NET assemblies (as we saw before this is not strictly necessary to use LINQ to explore .NET metadata, but it definitely can help to make the experience more pleasant/enabling some scenarios by, for instance, exposing some predefined Sources like all public typesmembers and the like).

You can read more about custom LINQPad drivers here, but in summary I created a static driver that exposes the following predefined query sources:
  • Types : A collection of all types (irrespective to accessibility and/or whether the type in question is a top level / inner type) from the assembly's main module.
  • PublicTypesA collection of all public types from the assembly's main module.
In addition to that I've also introduced some convenience functions that can be used in the queries:
  • AsString(method): An extension method for MethodBody that converts a method body into a formatted string with it's IL instructions.
  • Calls(method): An extension method for a MethodDefinition / MethodBody that takes a method (either as a MethodInfo or a MethodReference) and returns true if the method in question is being called in the method body.
  • References(type)An extension method for a MethodDefinition / MethodBody that takes a type (either as a string, a TypeInfo or a TypeReference) and returns true if the type in question is being referenced in the method body.
  • Property(name): An extension method (for TypeDefinition) that returns a property definition if the type has a matching one or null otherwise.
  • Event(name): An extension method (for TypeDefinition) that returns an event definition if the type has a matching one or null otherwise.
  • Field(name): An extension method (for TypeDefinition) that returns a field definition if the type has  a matching one or null otherwise.
You can have a glimpse of those features in the following screenshot:


If you are interested in using this, all you need to do is to download Cecil.LINQPad.Driver.lpx and install it in LINQPad by clicking Add connection "link" then "View more drivers...", then "Browse" and finally selecting Cecil.LINQPad.Driver.lpx file

Of course you can also grab the source code from GitHub and build it for yourself ;)

Feel free to comment, make suggestions, report issues, ask questions, etc.

Have fun!

Para o infinito e além: consultas ainda mais poderosas sob metadados NET com drivers parta LINQPad e Mono.Cecil

No post anterior mostrei como usar o LINQPad para executar consultas em metadados .NET usando .NET Reflection, técnica que, apesar de muito útil, possui algumas limitações:
  • Zero integração com LINQPad, exigindo que o assembly seja carregado manualmente (ainda pior é fato de termos que digitar o caminho completo do assembly),
  • Como o .Net Reflection não expõe o corpo (instruções) de métodos, não é possível por exemplo, encontrar todos os métodos que executam algum outro método específico ou que acessem uma propriedade, etc.
Felizmente o LINQPad nos permite desenvolver / utilizar custom drivers que se integram muito mais naturalmente com o mesmo. No meu caso eu escrevi um que usa o Mono.Cecil (ao invés de .Net Reflection) para expor algumas fontes pré-definidas para consultas:
  • Types : Uma lista com todos os tipos (independentemente do tipo ser público ou não e/ou ser uma inner class) definidos no módulo principal do assembly.
  • PublicTypes: Uma lista composta por todos os tipos definidos publicamente.
Também introduzi as seguintes extensões (extension methods):
  • AsString(): Método que estende MethodBody retornando as instruções que compõe o corpo do método na forma de uma string.
  • Calls(method): Método que estende MethodDefinition / MethodBody retornando verdadeiro (true) caso o método passado como parâmetro seja executado pelo corpo do método estendido.
  • References(type)Método que estende MethodDefinition / MethodBody retornando verdadeiro (true) se o tipo passado como parâmetro for referenciado pelas instruções do corpo do método estendido.
  • Property(name): Método que estende TypeDefinition retornando a propriedade especificada pelo parâmetro (caso o tipo possua uma propriedade com o mesmo nome) ou null.
  • Event(name): Método que estende TypeDefinition retornando o evento que possua o nome igual ao passado como parâmetro ou null caso o tipo em questão não possua um evento com este nome.
  • Field(name): Método que estende TypeDefinition retornando o campo que possua o nome igual ao especificado no parâmetro ou null caso o tipo em questão não possua um campo com este nome.
Você pode ver um exemplo da utilização de tais métodos na imagem abaixo:


Para usar este Custom Driver você pode baixar o arquivo Cecil.LINQPad.Driver.lpx e instalar o mesmo no LINQPad clicando na opção Add connection,  depois em "View more drivers...", "Browse" e finalmente selecionando o arquivo que você acabou de baixar (Cecil.LINQPad.Driver.lpx)

É claro que você também pode baixar o source do mesmo do GitHub e compilar / instalar ;)


Uma vez instalado, o mesmo aparecerá na lista de opções de drivers disponíveis:



Pressione o botão Next e, a seguir selecione o assembly que você deseja fazer consultas. Lembre-se de, após abrir o assembly, clicar no dropdown "Connection" para selecionar a conexão que o LINQPad deve usar para rodar as consultas, ou seja, qual assembly deve ser usado como fonte nas consultas. No exemplo abaixo o assembly Boo.Lang.Parser.dll será usado:

Pronto. Agora você já pode rodar suas consultas LINQ!

Have fun!