web-dev-qa-db-ja.com

C#では、ExpressionAPIはReflectionよりも優れていますか

今日、私はC#式APIを調査しています。そのため、ExpressionとReflectionの違いなど、それがどのように機能するかを理解するのに役立つことがあります。また、式が単なる構文糖衣であるのか、それともリフレクションよりも実際に優れているのかを理解したいと思いますパフォーマンス面で

良い例と良い記事へのリンクをいただければ幸いです。 :-)

43
Nawaz

1つのメソッドの呼び出しについて:

  • 直接通話はスピード的に打ち負かすことはできません。
  • Expression APIの使用は、速度的にReflection.EmitまたはDelegate.CreateDelegateを使用するのとグローバルに似ています(小さな違いを測定できます。常に測定や目標なしで速度を最適化することは無意味です)。

    それらはすべてILを生成し、フレームワークはある時点でそれをネイティブコードにコンパイルします。ただし、デリゲートを呼び出すための1つの間接レベルと、デリゲート内での1つのメソッド呼び出しのコストを支払う必要があります。

    式APIはさらに制限されていますが、ILを学習する必要がないため、使用が1桁簡単です。

  • 直接またはC#4のdynamicキーワードを介して使用される動的言語ランタイムは、少しオーバーヘッドを追加しますが、パラメータータイプ、アクセス、その他に関連するほとんどのチェックをキャッシュするため、コードの発行に近づきます。

    dynamicキーワードを介して使用すると、通常のメソッド呼び出しのように見えるため、最も適切な構文も取得されます。ただし、動的を使用する場合は、メソッド呼び出しに制限されますが、ライブラリはさらに多くのことを実行できます( IronPython を参照)

  • System.Reflection.MethodInfo.Invokeは遅い:他のどのメソッドに加えて、メソッドを呼び出すたびにMethodInfoに対してアクセス権をチェックし、引数の数、タイプ、...をチェックする必要があります。

Jon Skeetも、この回答でいくつかの良い点を取得しています: Delegate.CreateDelegate vs DynamicMethod vs Expression


いくつかのサンプル、同じことが異なる方法で行われました。

行数と複雑さから、どのソリューションを保守するのが簡単で、どのソリューションを長期的な保守の観点から避けるべきかをすでに理解できました。

ほとんどのサンプルは無意味ですが、C#の基本的なコード生成クラス/構文を示しています。詳細については、MSDNが常にあります。

PS:ダンプは LINQPad メソッドです。

public class Foo
{
    public string Bar(int value) { return value.ToString(); }
}

void Main()
{
    object foo = new Foo();

    // We have an instance of something and want to call a method with this signature on it :
    // public string Bar(int value);

    Console.WriteLine("Cast and Direct method call");
    {
        var result = ((Foo)foo).Bar(42);
        result.Dump();
    }
    Console.WriteLine("Create a lambda closing on the local scope.");
    {
        // Useless but i'll do it at the end by manual il generation

        Func<int, string> func = i => ((Foo)foo).Bar(i);
        var result = func(42);
        result.Dump();
    }
    Console.WriteLine("Using MethodInfo.Invoke");
    {
        var method = foo.GetType().GetMethod("Bar");
        var result = (string)method.Invoke(foo, new object[] { 42 });
        result.Dump();
    }
    Console.WriteLine("Using the dynamic keyword");
    {
        var dynamicFoo = (dynamic)foo;
        var result = (string)dynamicFoo.Bar(42);
        result.Dump();
    }
    Console.WriteLine("Using CreateDelegate");
    {
        var method = foo.GetType().GetMethod("Bar");
        var func = (Func<int, string>)Delegate.CreateDelegate(typeof(Func<int, string>), foo, method);
        var result = func(42);
        result.Dump();
    }
    Console.WriteLine("Create an expression and compile it to call the delegate on one instance.");
    {
        var method = foo.GetType().GetMethod("Bar");
        var thisParam = Expression.Constant(foo);
        var valueParam = Expression.Parameter(typeof(int), "value");
        var call = Expression.Call(thisParam, method, valueParam);
        var lambda = Expression.Lambda<Func<int, string>>(call, valueParam);
        var func = lambda.Compile();
        var result = func(42);
        result.Dump();
    }
    Console.WriteLine("Create an expression and compile it to a delegate that could be called on any instance.");
    {
        // Note that in this case "Foo" must be known at compile time, obviously in this case you want
        // to do more than call a method, otherwise just call it !
        var type = foo.GetType();
        var method = type.GetMethod("Bar");
        var thisParam = Expression.Parameter(type, "this");
        var valueParam = Expression.Parameter(typeof(int), "value");
        var call = Expression.Call(thisParam, method, valueParam);
        var lambda = Expression.Lambda<Func<Foo, int, string>>(call, thisParam, valueParam);
        var func = lambda.Compile();
        var result = func((Foo)foo, 42);
        result.Dump();
    }
    Console.WriteLine("Create a DynamicMethod and compile it to a delegate that could be called on any instance.");
    {
        // Same thing as the previous expression sample. Foo need to be known at compile time and need
        // to be provided to the delegate.

        var type = foo.GetType();
        var method = type.GetMethod("Bar");

        var dynamicMethod = new DynamicMethod("Bar_", typeof(string), new [] { typeof(Foo), typeof(int) }, true);
        var il = dynamicMethod.GetILGenerator();
        il.DeclareLocal(typeof(string));
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Call, method);
        il.Emit(OpCodes.Ret);
        var func = (Func<Foo, int, string>)dynamicMethod.CreateDelegate(typeof(Func<Foo, int, string>));
        var result = func((Foo)foo, 42);
        result.Dump();
    }
    Console.WriteLine("Simulate closure without closures and in a lot more lines...");
    {
        var type = foo.GetType();
        var method = type.GetMethod("Bar");

        // The Foo class must be public for this to work, the "skipVisibility" argument of
        // DynamicMethod.CreateDelegate can't be emulated without breaking the .Net security model.

        var Assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
            new AssemblyName("MyAssembly"), AssemblyBuilderAccess.Run);
        var module = Assembly.DefineDynamicModule("MyModule");
        var tb = module.DefineType("MyType", TypeAttributes.Class | TypeAttributes.Public);

        var fooField = tb.DefineField("FooInstance", type, FieldAttributes.Public);
        var barMethod = tb.DefineMethod("Bar_", MethodAttributes.Public, typeof(string), new [] { typeof(int) });
        var il = barMethod.GetILGenerator();
        il.DeclareLocal(typeof(string));
        il.Emit(OpCodes.Ldarg_0); // this
        il.Emit(OpCodes.Ldfld, fooField);
        il.Emit(OpCodes.Ldarg_1); // arg
        il.Emit(OpCodes.Call, method);
        il.Emit(OpCodes.Ret);

        var closureType = tb.CreateType();

        var instance = closureType.GetConstructors().Single().Invoke(new object[0]);

        closureType.GetField(fooField.Name).SetValue(instance, foo);

        var methodOnClosureType = closureType.GetMethod("Bar_");

        var func = (Func<int, string>)Delegate.CreateDelegate(typeof(Func<int, string>), instance,
            closureType.GetMethod("Bar_"));
        var result = func(42);
        result.Dump();
    }
}
48

リフレクションのパフォーマンスは遅くなります。それに関する良い記事については this 記事を参照してください。

4
Cornelius

この男は実際にそれを測定しました。

http://www.palmmedia.de/Blog/2012/2/4/reflection-vs-compiled-expressions-vs-delegates-performance-comparision

つまり、静的変数にキャッシュされて再利用されるコンパイル済みの式は、リフレクションよりも20倍高速に実行されます。

2
jazzcat