Menganimasikan pohon ekspresi dengan pembuatan kode

Pohon ekspresi System.Linq.Expressions



memungkinkan untuk mengekspresikan maksud tidak hanya dengan kode itu sendiri, tetapi juga oleh struktur dan sintaksnya.





Kreasi mereka dari ekspresi lambda sebenarnya adalah gula sintaksis, di mana kode biasa ditulis, dan kompilator membangun pohon sintaksis ( AST ) darinya , yang menyertakan referensi ke objek dalam memori, dan menangkap variabel. Hal ini memungkinkan Anda untuk memanipulasi tidak hanya data, tetapi juga kode dalam konteks penggunaannya: menulis ulang, menambah, mentransfer, dan hanya kemudian mengompilasi dan mengeksekusi.





Kompilasi run-time menghasilkan delegasi produktif yang seringkali lebih cepat daripada yang dikompilasi pada waktu pembuatan ( dengan biaya overhead yang lebih sedikit ). Namun, kompilasi itu sendiri membutuhkan waktu hingga puluhan ribu kali lebih lama daripada memanggil hasil kompilasi.





(patokan)

Bertindak





Waktu, ns





Memanggil Kompilasi Cache





0,5895 ± 0,0132 ns





Kompilasi dan Panggil





83.292.3139 ± 922.4315 ns









Hal ini sangat menyinggung ketika ekspresi sederhana, misalnya, hanya berisi akses ke properti (di perpustakaan untuk pemetaan, serialisasi, pengikatan data), panggilan ke konstruktor atau metode (untuk solusi IoC / DI).





Delegasi yang dikompilasi biasanya di-cache untuk digunakan kembali, tetapi ini tidak disimpan dalam skrip ketika akses pertama terjadi pada banyak waktu. Dalam kasus seperti itu, run-time dari ekspresi kompilasi menjadi signifikan dan menunda peluncuran aplikasi atau jendela individu.





Untuk mengurangi waktu yang dibutuhkan untuk mendapatkan delegasi dari pohon ekspresi, gunakan:





  • Interpretasi bawaan.

    Kebutuhan untuk menggunakan interpreter sebagai ganti compiler ditunjukkan oleh flag yang sesuai:





    Expression.Compile(preferInterpretation: true)
          
          



    Itu terjadi melalui refleksi, tetapi dengan overhead membentuk tumpukan instruksi.





    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





, .





, — , .





Flame plot pencarian benchmark dan pemanggilan delegasi akses properti yang dihasilkan
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)
          
          



    Untuk Roslyn Source Generators, properti yang diperlukan harus terlebih dahulu ditetapkan secara terpisah di grup MSBuild CompilerVisibleProperty



    dan baru kemudian dipanggil:





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



  • Dari generator, Anda dapat melempar peringatan dan membuat kesalahan.





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










All Articles