Animando árvores de expressão com geração de código

As árvores de expressão System.Linq.Expressions



tornam possível expressar intenções não apenas pelo código em si, mas também por sua estrutura e sintaxe.





Sua criação a partir de expressões lambda é, na verdade, um açúcar sintático, no qual o código comum é escrito, e o compilador constrói uma árvore sintática ( AST ) a partir dele , que inclui referências a objetos na memória e captura variáveis. Isso permite que você manipule não apenas os dados, mas também o código no contexto do qual eles são usados: reescrever, complementar, transferir e somente então compilar e executar.





A compilação em tempo de execução produz delegados produtivos que geralmente são mais rápidos do que aqueles compilados em tempo de construção ( ao custo de menos sobrecarga ). No entanto, a própria compilação leva dezenas de milhares de vezes mais do que chamar o resultado da compilação.





(benchmark)

Aja





Tempo, ns





Cached Compile Invoke





0,5895 ± 0,0132 ns





Compilar e invocar





83.292,3139 ± 922,4315 ns









Isso é especialmente ofensivo quando a expressão é simples, por exemplo, contém apenas acesso a uma propriedade (em bibliotecas para mapeamento, serialização, vinculação de dados), uma chamada a um construtor ou método (para soluções IoC / DI).





Delegados compilados geralmente são armazenados em cache para serem reutilizados, mas isso não salva em scripts quando o primeiro acesso acontece a um lote de cada vez. Nesses casos, o tempo de execução das expressões de compilação torna-se significativo e atrasa a inicialização do aplicativo ou das janelas individuais.





Para reduzir o tempo que leva para obter delegados de árvores de expressão, use:





  • Interpretação integrada.

    A necessidade de usar um interpretador em vez de um compilador é indicada pelo sinalizador correspondente:





    Expression.Compile(preferInterpretation: true)
          
          



    Acontece por reflexão, mas com a sobrecarga de formar uma pilha de instruções.





    Xamarin.iOS, Xamarin.watchOS, Xamarin.tvOS, Mono.PS4 Mono.XBox IL (System.Reflection.Emit



    ) .





  • FastExpressionCompile @dadhi.

    p IL .





    JIT Mono Interpreter.





  • .

    , .





    , . , Fasterflect, System.Reflection.Emit



    Mono Interpreter.





, , :





- (design-time) (compile-time).





compile-time .





API , . , , . - DI — , .





API , . : , run-time compile-time — . , — .





,





namespace Namespace
{
  public class TestClass
  {
    public int Property { get; set; }
  }
}
      
      



System.Linq.Expressions.Expression<T>







Expression<Func<TestClass, int>> expression = o => o.Property;
      
      







Func<object, object> _ = obj => ((Namespace.TestClass)obj).Property;
Action<object, object> _ => (t, m) => ((Namespace.TestClass)t).Property
  = (System.Int32)m;
      
      



:





namespace ExpressionDelegates.AccessorRegistration
{
  public static class ModuleInitializer
  {
    public static void Initialize()
    {
      ExpressionDelegates.Accessors.Add("Namespace.TestClass.Property",
        getter: obj => ((Namespace.TestClass)obj).Property,
        setter: (t, m) => ((Namespace.TestClass)t).Property = (System.Int32)m);
    }
  }
}
      
      



, , :





  • Microsoft.CodeDom,





  • T4,





  • PostSharp,





  • Fody,





  • Roslyn Source Generators.





, Roslyn Source Generators C# .





, Roslyn Source Generators , . . Roslyn API, code-fix.





Roslyn Source Generators - ( !) .





:





namespace Microsoft.CodeAnalysis
{
  public interface ISourceGenerator
  {
    void Initialize(GeneratorInitializationContext context);
    void Execute(GeneratorExecutionContext context);
  }
}
      
      



.





Initialize



- . GeneratorInitializationContext



.





Execute



, , , , .





Roslyn SyntaxTree



:





GeneratorExecutionContext.Compilation.SyntaxTrees
      
      



:





semanticModel =
  GeneratorExecutionContext.Compilation.GetSemanticModel(SyntaxTree)
      
      



, ( ) , , .





- System.Linq.Expressions.Expression<T>



- , , :





, (Symbol



), :





  • , ;





  • ;





  • IsStatic



    , IsConst



    , IsReadOnly



    .





.





Roslyn API (Microsoft.CodeAnalysis



) , c API (System.Reflection



). ISymbol.ToDisplayString(SymbolDisplayFormat)



c :





/, :





:





var sourceBuilder = new StringBuilder(
@"namespace ExpressionDelegates.AccessorRegistration
{
  public static class ModuleInitializer
  {
    public static void Initialize()
    {");

      foreach (var line in registrationLines)
      {
        sourceBuilder.AppendLine();
        sourceBuilder.Append(' ', 6).Append(line);
      }

      sourceBuilder.Append(@"
    }
  }
}");

GeneratorExecutionContext.AddSource(
  "AccessorRegistration",
  SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
      
      



... :)





, Source Generators , C# 9+. .NET 5.





Roslyn Source Generators API .NET Standard, .NET Core, .NET Framework Xamarin Uno.SourceGeneration.





Uno.SourceGeneration ISourceGenerator [Generator], # 9 Microsoft.CodeAnalysis



Uno:





using Uno.SourceGeneration;
using GeneratorAttribute = Uno.SourceGeneration.GeneratorAttribute;
using ISourceGenerator = Uno.SourceGeneration.ISourceGenerator;
      
      



.

, :





<ItemGroup>
  <SourceGenerator Include="PATH\TO\GENERATOR.dll" />
</ItemGroup>
      
      



, nuget, MSBuild props :









API , , .





Module Initializer. ( ), . CLR, , C# c [ModuleInitializer]



9 .





FodyFody.ModuleInit



. ModuleInitializer



. .





Fody.ModuleInit



MSBuild FodyWeavers.xml



Weaver- Fody .





, :





  1. Source Generator , , ModuleInitializer



    .





  2. Fody.ModuleInit



    ModuleInitializer



    .





  3. ModuleInitializer



    , .





:





Expression<Func<string, int>> expression = s => s.Length;

MemberInfo accessorInfo = ((MemberExpression)expression.Body).Member;
Accessor lengthAccessor = ExpressionDelegates.Accessors.Find(accessorInfo);

var length = lengthAccessor.Get("17 letters string");
// length == 17
      
      



, :





, - .









,









4.6937 ± 0.0443









5.8940 ± 0.0459









191.1785 ± 2.0766









88,701.7674 ± 962.4325

















1.7740 ± 0.0291









5.8792 ± 0.1525









163.2990 ± 1.4388









88,103.7519 ± 235.3721

















1.1767 ± 0.0289









4.1000 ± 0.0185









186.4856 ± 2.5224









83,292.3139 ± 922.4315





, .





, — , .





Gráfico de chama de pesquisa de benchmark e invocação de delegado de acesso de propriedade gerado
Flame-

System.Reflection.MemberInfo



. .





.





: github/ExpressionDelegates, nuget.





, Source Generators :





  • Source Generator Playground (github).

    Roslyn Source Generators , .





  • Visual Studio.

    Roslyn Syntax API .





  • Source Generator . .

    Visual Studio «Just-In-Time debugger» Tools -> Options -> Debugging -> Just-In-Time Debugging -> ☑ Managed



    .





  • *.cs



    , Visual Studio 16.8.

    Uno.SourceGeneration : \obj\{configuration}\{platform}\g\



    .

    Roslyn Source Generators MSBuild EmitCompilerGeneratedFiles



    .

    : \obj\{configuration}\{platform}\generated\



    , CompilerGeneratedFilesOutputPath



    .





  • Source Generators MSBuild.

    Uno.SourceGeneration





    GeneratorExecutionContext.GetMSBuildPropertyValue(string)
          
          



    Para Roslyn Source Generators, as propriedades necessárias devem primeiro ser designadas separadamente no grupo MSBuild CompilerVisibleProperty



    e só então chamadas:





    GeneratorExecutionContext.AnalyzerConfigOptions.GlobalOptions
      .TryGetValue("build_property.<PROPERTY_NAME>", out var propertyValue)
          
          



  • Do gerador, você pode lançar avisos e erros de construção.





    //Roslyn Source Generators
    GeneratorExecutionContext.ReportDiagnostic(Diagnostic)
    //Uno.SourceGeneration:
    GeneratorExecutionContext.GetLogger().Warn/Error().
          
          










All Articles