Animando árboles de expresión con generación de código

Los árboles de expresión System.Linq.Expressions



permiten expresar intenciones no solo por el código en sí, sino también por su estructura y sintaxis.





Su creación a partir de expresiones lambda es, de hecho, azúcar sintáctica, en la que se escribe código ordinario y el compilador construye un árbol de sintaxis ( AST ) a partir de él , que incluye referencias a objetos en la memoria y captura variables. Esto le permite manipular no solo los datos, sino también el código en el contexto en el que se utilizan: reescribir, complementar, transferir y solo luego compilar y ejecutar.





La compilación en tiempo de ejecución produce delegados productivos, que a menudo son más rápidos que los compilados en el tiempo de compilación ( a costa de menos gastos generales ). Sin embargo, la compilación en sí tarda decenas de miles de veces más que llamar al resultado de la compilación.





(punto de referencia)

Actuar





Tiempo, ns





Invocación de compilación en caché





0,5895 ± 0,0132 ns





Compilar e invocar





83,292.3139 ± 922.4315 ns









Esto es especialmente ofensivo cuando la expresión es simple, por ejemplo, solo contiene acceso a una propiedad (en bibliotecas para mapeo, serialización, enlace de datos), una llamada a un constructor o método (para soluciones IoC / DI).





Los delegados compilados generalmente se almacenan en caché para ser reutilizados, pero esto no se guarda en scripts cuando el primer acceso ocurre a muchos a la vez. En tales casos, el tiempo de ejecución de la compilación de las expresiones se vuelve significativo y retrasa el lanzamiento de la aplicación o ventanas individuales.





Para reducir el tiempo que lleva obtener delegados de árboles de expresión, use:





  • Interpretación incorporada.

    La necesidad de utilizar un intérprete en lugar de un compilador se indica con la bandera correspondiente:





    Expression.Compile(preferInterpretation: true)
          
          



    Sucede a través de la reflexión, pero con la sobrecarga de formar una pila de instrucciones.





    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 llama de la búsqueda de referencia e invocación del delegado de acceso a la propiedad generado
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 los generadores de origen Roslyn, las propiedades requeridas primero deben designarse por separado en el grupo MSBuild CompilerVisibleProperty



    y solo luego deben llamarse:





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



  • Desde el generador, puede lanzar advertencias y generar errores.





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










All Articles