例によるDOTNETコード生成概要



導入
コード生成は非常に興味深いトピックです.コードを書く代わりに、コードを書くコードを書くことができます.コンパイル時にコード生成を行うことができます(新しいファンシーソースジェネレータ)と実行時(式では、ilを出力).とにかく、ランタイムでメソッドとクラスを作成するアイデアは私に魔法のように聞こえる.私は十分な理解を持っている今、私は私はコード生成を使用して、より効率的でエレガントな方法で解決することがいくつかのタスクを持っていたことに気づいた.残念ながら、私はそれについて何も知りませんでした.インターネットを検索すると、かなり高いエントリの閾値で結果を与え、彼らは機能の全体の理解を与えなかった.記事の例のほとんどは全く些細なので、まだそれを実際に適用する方法は不明です.ここでは、最初のステップとして解決することができる特定の問題を記述したいmetaprogramming そして、異なるコード生成アプローチの概観を与える.たくさんのコードがあります.

タスク説明
我々のアプリケーションがいくつかの源からストリングの配列としてデータを受け取ると想像しましょう(単純なために、ストリング、整数とdatetime値は入力配列で予想されます).
["John McClane", "1994-11-05T13:15:30", "4455"]
この入力を特定のクラスのインスタンスに解析する一般的な方法が必要です.これはパーサデリゲートを作成するインターフェイスですT 出力)
public interface IParserFactory
{
    Func<string[], T> GetParser<T>() where T : new();
}
私の使用ParserOutputAttribute パーサーの出力として使用するクラスを識別します.と私はArrayIndexAttribute 配列の要素のそれぞれに対応するプロパティを理解するには、次の手順に従います.
[ParserOutput]
public class Data
{
    [ArrayIndex(0)] public string Name { get; set; } // will be "John McClane"
    [ArrayIndex(2)] public int Number { get; set; } // will be 4455
    [ArrayIndex(1)] public DateTime Birthday { get; set; } // will be 1994-11-05T13:15:30
}
配列要素が対象型に解析できない場合は無視されます.
一般的な考えとして、実装を制限したくないData クラスのみ.適切な属性を持つ任意の型のパーサーデリゲートを作成します.

平時C
まず最初に、既知の型に対して、コード生成やリフレクションなしで単純なCチェックコードを記述します.
var data = new Data();
if (0 < inputArray.Length)
{
    data.Name = inputArray[0];
}
if (1 < inputArray.Length && DateTime.TryParse(inputArray[1], out var bd))
{
    data.Birthday = bd;
}
if (2 < inputArray.Length && int.TryParse(inputArray[2], out var n))
{
    data.Number = n;
}
return data;
全く簡単、右?しかし、実行時やコンパイル時に任意の型に対して同じコードを生成したいと思います.レッツゴー!

反射
との最初のアプローチでreflection パーサデリゲートを生成するつもりはありません.代わりに、ターゲット型のインスタンスを作成し、リフレクションAPIを使用してプロパティを設定します.
public class ReflectionParserFactory : IParserFactory
{
    public Func<string[], T> GetParser<T>() where T : new()
    {
        return ArrayIndexParse<T>;
    }

    private static T ArrayIndexParse<T>(string[] data) where T : new()
    {
        // create a new instance of target type
        var instance = new T();
        var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);

        //go through all public and non-static properties
        //read and parse corresponding element in array and if success - set property value
        for (int i = 0; i < props.Length; i++)
        {
            var attrs = props[i].GetCustomAttributes(typeof(ArrayIndexAttribute)).ToArray();
            if (attrs.Length == 0) continue;

            int order = ((ArrayIndexAttribute)attrs[0]).Order;
            if (order < 0 || order >= data.Length) continue;

            if (props[i].PropertyType == typeof(string))
            {
                props[i].SetValue(instance, data[order]);
                continue;
            }

            if (props[i].PropertyType == typeof(int))
            {
                if (int.TryParse(data[order], out var intResult))
                {
                    props[i].SetValue(instance, intResult);
                }

                continue;
            }

            if (props[i].PropertyType == typeof(DateTime))
            {
                if (DateTime.TryParse(data[order], out var dtResult))
                {
                    props[i].SetValue(instance, dtResult);
                }
            }
        }
        return instance;
    }
}
それは動作し、それはかなり読みやすいです.でもそれはslow チェックbenchmarks セクションも参照).このコードを非常に頻繁に呼び出したいならば、それは問題でありえました.私は実際のコード生成を使用してより洗練された何かを実装したい.

コード生成

エクスプレッションツリー
からofficial documentation :

Expression trees represent code in a tree-like data structure, where each node is an expression, for example, a method call or a binary operation such as x < y. You can compile and run code represented by expression trees.

  • How to execute expression trees
  • Expression classes
  • 式木は、原始的なビルディングブロックを与えますExpression.Call メソッドを呼び出すにはExpression.Loop 繰り返しの論理などを追加するには、これらのブロックを使用してtree 命令の最後に、実行時にデリゲートにコンパイルします.
    public class ExpressionTreeParserFactory : IParserFactory
    {
        public Func<string[], T> GetParser<T>() where T : new()
        {
            var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
    
            //declare an input parameter of the delegate
            ParameterExpression inputArray = Expression.Parameter(typeof(string[]), "inputArray");
            //declare an output parameter of the delegate
            ParameterExpression instance = Expression.Variable(typeof(T), "instance");
    
            //create a new instance of target type
            var block = new List<Expression>
            {
                Expression.Assign(instance, Expression.New(typeof(T).GetConstructors()[0]))
            };
            var variables = new List<ParameterExpression> {instance};
    
            //go through all public and non-static properties
            foreach (var prop in props)
            {
                var attrs = prop.GetCustomAttributes(typeof(ArrayIndexAttribute)).ToArray();
                if (attrs.Length == 0) continue;
    
                int order = ((ArrayIndexAttribute)attrs[0]).Order;
                if (order < 0) continue;
    
                //validate an index from ArrayIndexAttribute
                var orderConst = Expression.Constant(order);
                var orderCheck = Expression.LessThan(orderConst, Expression.ArrayLength(inputArray));
    
                if (prop.PropertyType == typeof(string))
                {
                    //set string property
                    var stringPropertySet = Expression.Assign(
                        Expression.Property(instance, prop),
                        Expression.ArrayIndex(inputArray, orderConst));
    
                    block.Add(Expression.IfThen(orderCheck, stringPropertySet));
                    continue;
                }
    
                //get parser method from the list of available parsers (currently we parse only Int and DateTime)
                if (!TypeParsers.Parsers.TryGetValue(prop.PropertyType, out var parser))
                {
                    continue;
                }
    
                var parseResult = Expression.Variable(prop.PropertyType, "parseResult");
                var parserCall = Expression.Call(parser, Expression.ArrayIndex(inputArray, orderConst), parseResult);
                var propertySet = Expression.Assign(
                    Expression.Property(instance, prop),
                    parseResult);
    
                //set property if an element of array is successfully parsed
                var ifSet = Expression.IfThen(parserCall, propertySet);
    
                block.Add(Expression.IfThen(orderCheck, ifSet));
                variables.Add(parseResult);
            }
    
            block.Add(instance);
    
            //compile lambda expression into delegate
            return Expression.Lambda<Func<string[], T>>(
                Expression.Block(variables.ToArray(), Expression.Block(block)), 
                inputArray).Compile();
        }
    }
    

    放出する
    DotNetコンパイラはC言語のコードを中間言語に変換しますCIL or just IL ) それから、dotnetランタイムはILを機械命令に翻訳します.例えば、sharplab.io 簡単にILがどのように見えるか確認できます.
  • System.Reflection.Emit
  • How to: Define and Execute Dynamic Methods
  • OpCodes list
  • ReSharper IL viewer
  • ここでは、(“EIT”)IL命令を直接書き出し、実行時にデリゲートにコンパイルします.
    public class EmitIlParserFactory : IParserFactory
    {
        public Func<string[], T> GetParser<T>() where T : new()
        {
            var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
    
            var dm = new DynamicMethod($"from_{typeof(string[]).FullName}_to_{typeof(T).FullName}", 
                typeof(T), new [] { typeof(string[]) }, typeof(EmitIlParserFactory).Module);
            var il = dm.GetILGenerator();
    
            //create a new instance of target type
            var instance = il.DeclareLocal(typeof(T));
            il.Emit(OpCodes.Newobj, typeof(T).GetConstructors()[0]);
            il.Emit(OpCodes.Stloc, instance);
    
            //go through all public and non-static properties
            foreach (var prop in props)
            {
                var attrs = prop.GetCustomAttributes(typeof(ArrayIndexAttribute)).ToArray();
                if (attrs.Length == 0) continue;
    
                int order = ((ArrayIndexAttribute)attrs[0]).Order;
                if (order < 0) continue;
    
                var label = il.DefineLabel();
    
                if (prop.PropertyType == typeof(string))
                {
                    //check whether order from ArrayIndexAttribute is a valid index of the input array
                    il.Emit(OpCodes.Ldc_I4, order);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldlen);
                    il.Emit(OpCodes.Bge_S, label);
    
                    //set string property
                    il.Emit(OpCodes.Ldloc, instance);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldc_I4, order);
                    il.Emit(OpCodes.Ldelem_Ref);
                    il.Emit(OpCodes.Callvirt, prop.GetSetMethod());
    
                    il.MarkLabel(label);
                    continue;
                }
    
                //get parser method from the list of available parsers (currently we parse only Int and DateTime)
                if (!TypeParsers.Parsers.TryGetValue(prop.PropertyType, out var parser))
                {
                    continue;
                }
    
                //check whether order from ArrayIndexAttribute is a valid index of the input array
                il.Emit(OpCodes.Ldc_I4, order);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldlen);
                il.Emit(OpCodes.Bge_S, label);
    
                var parseResult = il.DeclareLocal(prop.PropertyType);
    
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldc_I4, order);
                il.Emit(OpCodes.Ldelem_Ref);
                il.Emit(OpCodes.Ldloca, parseResult);
                il.EmitCall(OpCodes.Call, parser, null);
                il.Emit(OpCodes.Brfalse_S, label);
    
                //set property if an element of array is successfully parsed
                il.Emit(OpCodes.Ldloc, instance);
                il.Emit(OpCodes.Ldloc, parseResult);
                il.Emit(OpCodes.Callvirt, prop.GetSetMethod());
    
                il.MarkLabel(label);
            }
    
            il.Emit(OpCodes.Ldloc, instance);
            il.Emit(OpCodes.Ret);
    
            //create delegate from il instructions
            return (Func<string[], T>)dm.CreateDelegate(typeof(Func<string[], T>));
        }
    }
    

    シグイル
  • A fail-fast validating helper for .NET CIL generation
  • このアプローチは前のものと全く似ていますが、現在SIGILを使用しています.SIGILは構文の砂糖とよりわかりやすいエラーメッセージを与えます.
    public class SigilParserFactory : IParserFactory
    {
        public Func<string[], T> GetParser<T>() where T : new()
        {
            var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
    
            var il = Emit<Func<string[], T>>.NewDynamicMethod($"from_{typeof(string[]).FullName}_to_{typeof(T).FullName}");
    
            var instance = il.DeclareLocal<T>();
            il.NewObject<T>();
            il.StoreLocal(instance);
    
            foreach (var prop in props)
            {
                var attrs = prop.GetCustomAttributes(typeof(ArrayIndexAttribute)).ToArray();
                if (attrs.Length == 0) continue;
    
                int order = ((ArrayIndexAttribute)attrs[0]).Order;
                if (order < 0) continue;
    
                var label = il.DefineLabel();
    
                if (prop.PropertyType == typeof(string))
                {
                    il.LoadConstant(order);
                    il.LoadArgument(0);
                    il.LoadLength<string>();
                    il.BranchIfGreaterOrEqual(label);
    
                    il.LoadLocal(instance);
                    il.LoadArgument(0);
                    il.LoadConstant(order);
                    il.LoadElement<string>();
                    il.CallVirtual(prop.GetSetMethod());
    
                    il.MarkLabel(label);
                    continue;
                }
    
                if (!TypeParsers.Parsers.TryGetValue(prop.PropertyType, out var parser))
                {
                    continue;
                }
    
                il.LoadConstant(order);
                il.LoadArgument(0);
                il.LoadLength<string>();
                il.BranchIfGreaterOrEqual(label);
    
                var parseResult = il.DeclareLocal(prop.PropertyType);
    
                il.LoadArgument(0);
                il.LoadConstant(order);
                il.LoadElement<string>();
                il.LoadLocalAddress(parseResult);
                il.Call(parser);
                il.BranchIfFalse(label);
    
                il.LoadLocal(instance);
                il.LoadLocal(parseResult);
                il.CallVirtual(prop.GetSetMethod());
    
                il.MarkLabel(label);
            }
    
            il.LoadLocal(instance);
            il.Return();
    
            return il.CreateDelegate();
        }
    }
    

    キャッシュコンパイルパーサー
    我々は、パーサーデリゲートを作成するために3つのアプローチを実装しました:式木、ilとsigilを放出します.すべてのケースでは、同じ問題があります.IParserFactory.GetParser あなたがそれを呼ぶたびに、ハード仕事(表現木を構築するか、ILを放出して、それから委任を作成して)をします.解決策はとても簡単です-ただキャッシュしてください.
    public class CachedParserFactory : IParserFactory
    {
        private readonly IParserFactory _realParserFactory;
        private readonly ConcurrentDictionary<string, Lazy<object>> _cache;
    
        public CachedParserFactory(IParserFactory realParserFactory)
        {
            _realParserFactory = realParserFactory;
            _cache = new ConcurrentDictionary<string, Lazy<object>>();
        }
    
        public Func<string[], T> GetParser<T>() where T : new()
        {
            return (Func<string[], T>)(_cache.GetOrAdd($"aip_{_realParserFactory.GetType().FullName}_{typeof(T).FullName}", 
                new Lazy<object>(() => _realParserFactory.GetParser<T>(), LazyThreadSafetyMode.ExecutionAndPublication)).Value);
        }
    }
    
    今より効率的なデリゲートのコンパイルされたバージョンを再利用します.

    ロスリンベースアプローチ
    Roslynはコードをコンパイルするだけでなく、構文解析をしてコードを生成する能力を与えるdotnetコンパイラプラットフォームです.

    Roslynランタイムコード生成
  • Using Roslyn to build object to object mapper
  • Roslynのアプローチは非常に興味深いです.なぜなら、ILL命令を書くか、式ツリーブロックを組み合わせる代わりに、C言語を書くことができます.
    public static class RoslynParserInitializer
    {
        public static IParserFactory CreateFactory()
        {
            //get all types marked with ParserOutputAttribute
            var targetTypes =
                (from a in AppDomain.CurrentDomain.GetAssemblies()
                    from t in a.GetTypes()
                    let attributes = t.GetCustomAttributes(typeof(ParserOutputAttribute), true)
                    where attributes != null && attributes.Length > 0
                    select t).ToArray();
    
            var typeNames = new List<(string TargetTypeName, string TargetTypeFullName, string TargetTypeParserName)>();
            var builder = new StringBuilder();
            builder.AppendLine(@"
    using System;
    using Parsers.Common;
    
    public class RoslynGeneratedParserFactory : IParserFactory 
    {");
            //go through all types
            foreach (var targetType in targetTypes)
            {
                var targetTypeName = targetType.Name;
                var targetTypeFullName = targetType.FullName;
                var targetTypeParserName = targetTypeName + "Parser";
                typeNames.Add((targetTypeName, targetTypeFullName, targetTypeParserName));
    
                //generate private parser method for each target type
                builder.AppendLine($"private static T {targetTypeParserName}<T>(string[] input)");
    
                builder.Append($@"
    {{
    var {targetTypeName}Instance = new {targetTypeFullName}();");
    
                var props = targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
    
                //go through all properties of the target type
                foreach (var prop in props)
                {
                    var attrs = prop.GetCustomAttributes(typeof(ArrayIndexAttribute)).ToArray();
                    if (attrs.Length == 0) continue;
    
                    int order = ((ArrayIndexAttribute)attrs[0]).Order;
                    if (order < 0) continue;
    
                    if (prop.PropertyType == typeof(string))
                    {
                        builder.Append($@"
    if({order} < input.Length)
    {{
    {targetTypeName}Instance.{prop.Name} = input[{order}];
    }}
    ");
                    }
    
                    if (prop.PropertyType == typeof(int))
                    {
                        builder.Append($@"
    if({order} < input.Length && int.TryParse(input[{order}], out var parsed{prop.Name}))
    {{
    {targetTypeName}Instance.{prop.Name} = parsed{prop.Name};
    }}
    ");
                    }
    
                    if (prop.PropertyType == typeof(DateTime))
                    {
                        builder.Append($@"
    if({order} < input.Length && DateTime.TryParse(input[{order}], out var parsed{prop.Name}))
    {{
    {targetTypeName}Instance.{prop.Name} = parsed{prop.Name};
    }}
    ");
                    }
                }
    
                builder.Append($@"
    object obj = {targetTypeName}Instance;
    return (T)obj;
    }}");
            }
    
            builder.AppendLine("public Func<string[], T> GetParser<T>() where T : new() {");
            foreach (var typeName in typeNames)
            {
                builder.Append($@"
    if (typeof(T) == typeof({typeName.TargetTypeFullName}))
    {{
    return {typeName.TargetTypeParserName}<T>;
    }}
    ");
            }
            builder.AppendLine("throw new NotSupportedException();}");
    
            builder.AppendLine("}");
    
            var syntaxTree = CSharpSyntaxTree.ParseText(builder.ToString());
    
            //reference assemblies
            string assemblyName = Path.GetRandomFileName();
            var refPaths = new List<string> {
                typeof(Object).GetTypeInfo().Assembly.Location,
                typeof(Enumerable).GetTypeInfo().Assembly.Location,
                Path.Combine(Path.GetDirectoryName(typeof(GCSettings).GetTypeInfo().Assembly.Location), "System.Runtime.dll"),
                typeof(RoslynParserInitializer).GetTypeInfo().Assembly.Location,
                typeof(IParserFactory).GetTypeInfo().Assembly.Location,
                Path.Combine(Path.GetDirectoryName(typeof(GCSettings).GetTypeInfo().Assembly.Location), "netstandard.dll"),
            };
            refPaths.AddRange(targetTypes.Select(x => x.Assembly.Location));
    
            var references = refPaths.Select(r => MetadataReference.CreateFromFile(r)).ToArray();
    
            // compile dynamic code
            var compilation = CSharpCompilation.Create(
                assemblyName,
                syntaxTrees: new[] { syntaxTree },
                references: references,
                options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
    
            //compile assembly
            using (var ms = new MemoryStream())
            {
                var result = compilation.Emit(ms);
    
                //to get a proper errors
                if (!result.Success)
                {
                    throw new Exception(string.Join(",", result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error).Select(x => x.GetMessage())));
                }
                ms.Seek(0, SeekOrigin.Begin);
    
                // load assembly from memory
                var assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
    
                var factoryType = assembly.GetType("RoslynGeneratedParserFactory");
                if (factoryType == null) throw new NullReferenceException("Roslyn generated parser type not found");
    
                //create an instance of freshly generated parser factory
                return (IParserFactory)Activator.CreateInstance(factoryType);
            }
        }
    }
    

    発生源
  • からの発生源発生器の概観official documentation
  • ソースジェネレータは、コンパイルステップ中、すなわち事前にパーサーのデリゲートを構築する非常に興味深い能力を提供します.そのような場合には、最初にパーサのデリゲートを構築するためのランタイムオーバーヘッドはありません.
    [Generator]
    public class ParserSourceGenerator : ISourceGenerator
    {
        public void Initialize(GeneratorInitializationContext context)
        {
            //uncomment to debug
            //System.Diagnostics.Debugger.Launch();
        }
    
        public void Execute(GeneratorExecutionContext context)
        {
            var compilation = context.Compilation;
            var parserOutputTypeSymbol = compilation.GetTypeByMetadataName("Parsers.Common.ParserOutputAttribute");
            var attributeIndexTypeSymbol = compilation.GetTypeByMetadataName("Parsers.Common.ArrayIndexAttribute");
            var typesToParse = new List<ITypeSymbol>();
    
            foreach (var syntaxTree in compilation.SyntaxTrees)
            {
                var semanticModel = compilation.GetSemanticModel(syntaxTree);
    
                //get all types marked with ParserOutputAttribute
                typesToParse.AddRange(syntaxTree.GetRoot()
                    .DescendantNodesAndSelf()
                    .OfType<ClassDeclarationSyntax>()
                    .Select(x => semanticModel.GetDeclaredSymbol(x))
                    .OfType<ITypeSymbol>()
                    .Where(x => x.GetAttributes().Select(a => a.AttributeClass)
                        .Any(b => b == parserOutputTypeSymbol)));
            }
    
            var typeNames = new List<(string TargetTypeName, string TargetTypeFullName, string TargetTypeParserName)>();
            var builder = new StringBuilder();
            builder.AppendLine(@"
    using System;
    using Parsers.Common;
    namespace BySourceGenerator
    {
    public class Parser : IParserFactory 
    {");
    
            //go through all types
            foreach (var typeSymbol in typesToParse)
            {
                var targetTypeName = typeSymbol.Name;
                var targetTypeFullName = GetFullName(typeSymbol);
                var targetTypeParserName = targetTypeName + "Parser";
                typeNames.Add((targetTypeName, targetTypeFullName, targetTypeParserName));
                builder.AppendLine($"private static T {targetTypeParserName}<T>(string[] input)");
    
                builder.Append($@"
    {{
    var {targetTypeName}Instance = new {targetTypeFullName}();");
    
                var props = typeSymbol.GetMembers().OfType<IPropertySymbol>();
    
                //go through all properties of the target type
                foreach (var prop in props)
                {
                    var attr = prop.GetAttributes().FirstOrDefault(x => x.AttributeClass == attributeIndexTypeSymbol);
                    if (attr == null || !(attr.ConstructorArguments[0].Value is int)) continue;
    
                    int order = (int) attr.ConstructorArguments[0].Value;
                    if (order < 0) continue;
    
                    if (GetFullName(prop.Type) == "System.String")
                    {
                        builder.Append($@"
    if({order} < input.Length)
    {{
    {targetTypeName}Instance.{prop.Name} = input[{order}];
    }}
    ");
                    }
    
                    if (GetFullName(prop.Type) == "System.Int32")
                    {
                        builder.Append($@"
    if({order} < input.Length && int.TryParse(input[{order}], out var parsed{prop.Name}))
    {{
    {targetTypeName}Instance.{prop.Name} = parsed{prop.Name};
    }}
    ");
                    }
    
                    if (GetFullName(prop.Type) == "System.DateTime")
                    {
                        builder.Append($@"
    if({order} < input.Length && DateTime.TryParse(input[{order}], out var parsed{prop.Name}))
    {{
    {targetTypeName}Instance.{prop.Name} = parsed{prop.Name};
    }}
    ");
                    }
                }
    
                builder.Append($@"
    object obj = {targetTypeName}Instance;
    return (T)obj;
    }}");
            }
    
            builder.AppendLine("public Func<string[], T> GetParser<T>() where T : new() {");
            foreach (var typeName in typeNames)
            {
                builder.Append($@"
    if (typeof(T) == typeof({typeName.TargetTypeFullName}))
    {{
    return {typeName.TargetTypeParserName}<T>;
    }}
    ");
            }
    
            builder.AppendLine("throw new NotSupportedException();}");
    
            builder.AppendLine("}}");
    
            var src = builder.ToString();
            context.AddSource(
                "ParserGeneratedBySourceGenerator.cs",
                SourceText.From(src, Encoding.UTF8)
            );
        }
    
        private static string GetFullName(ITypeSymbol typeSymbol) =>
            $"{typeSymbol.ContainingNamespace}.{typeSymbol.Name}";
    }
    

    ベンチマーク
    ポストはベンチマークなしで包括的でありません.二つのことを比較したいです.
  • ステップアップ、すなわちパーサの生成
  • すでに生成されたパーサーの呼び出し.
  • ベンチマークはBenchmarkDotNet . μs - マイクロセカンドns - ナノ秒,1μs=1000 ns
    
    BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1237 (21H1/May2021Update)
    Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
    .NET SDK=5.0.401
      [Host]     : .NET 5.0.10 (5.0.1021.41214), X64 RyuJIT
      DefaultJob : .NET 5.0.10 (5.0.1021.41214), X64 RyuJIT
    

    パーサの生成
    方法
    平均
    エラー
    stddev
    ジェン0
    ジェン1
    ゲン2
    割り当てた
    エミテン
    22.02μs
    0.495μs
    1.429μs
    1.2817
    0.6409
    0.0305
    5 KB
    エクスプレッションツリー
    683.68μs
    13.609μs
    31.268μs
    2.9297
    0.9766
    -
    14 KB
    シグイル
    642.63μs
    12.305μs
    29.243μs
    112.3047
    -
    -
    460 KB
    ロスリン
    71605.64μs
    2533.732μs
    7350.817μs
    1000.0000
    -
    -
    5826 KB

    パーサーの呼び出し
    方法
    平均
    エラー
    stddev
    比率
    聯合
    ジェン0
    割り当てた
    エミテン
    374.7 ns
    7.75 ns
    22.36 ns
    1.02
    0.08
    0.0095
    40 b
    エクスプレッションツリー
    378.1 ns
    7.56 ns
    20.57 ns
    1.03
    0.08
    0.0095
    40 b
    反射
    13625.0 ns
    272.60 ns
    750.81 ns
    37.29
    2.29
    0.7782
    3256 B
    シグイル
    378.9 ns
    7.69 ns
    21.06 ns
    1.03
    0.07
    0.0095
    40 b
    ロスリン
    404.2 NS
    7.55 ns
    17.80 ns
    1.10
    0.07
    0.0095
    40 b
    発電機
    384.4 ns
    7.79 ns
    21.46 ns
    1.05
    0.08
    0.0095
    40 b
    マニュアル
    367.8 ns
    7.36 ns
    15.68 ns
    1.00
    0.00
    0.0095
    40 b
    反射の直接の使用の他のすべてのアプローチは、結果をほとんど同じにしますmanually written C# parser .

    ソースコード
    こちらgithub repository パーサーファクトリ、ユニットテスト、ベンチマーク.