如何将属性getter作为Func <object,object>?

时间:2017-10-14 10:22:29

标签: c# optimization lambda

简介 我正在研究一些严重依赖于反射的代码。一些核心部分被重复使用。即使是以最轻微的方式对其进行优化也会给我很多(不同程度)的性能优势。

上下文 核心包括从对象中获取数据。目前我使用反射来获取PropertyInfo对象,然后使用以下代码构造一个Func&lt; object,object&gt;:

    public static Func<object, object> BuildUntypedGetter(PropertyInfo propertyInfo)
    {
    var targetType = propertyInfo.DeclaringType;
    var methodInfo = propertyInfo.GetGetMethod();
    var exTarget = System.Linq.Expressions.Expression.Parameter(typeof(object), "t");
    var exBody0 = System.Linq.Expressions.Expression.Convert(exTarget, targetType);
    var exBody = System.Linq.Expressions.Expression.Call(exBody0, methodInfo);
    var exBody2 = System.Linq.Expressions.Expression.Convert(exBody, typeof(object));

    var lambda = System.Linq.Expressions.Expression.Lambda<Func<object, object>>(exBody2, exTarget);

    var action = lambda.Compile();
    return action;
    }

这得到的Func&lt; object,object&gt;是我然后缓存和使用。没有在其他对象类型上调用它的实际类型和安全性,但原始是我不必担心的事情,因为这是照顾。

在上面的代码中,创建的lambda如下所示:

.Lambda #Lambda1<System.Func`2[System.Object,System.Object]>(System.Object $t) {
    (System.Object).Call ((MyObjectType)$t).get_Id()
}

其中Id是在其上生成的属性。

如您所见,这只是一个重定向。它所做的就是召唤吸气剂。

问题 有没有一种方法可以将getter(get_Id)作为Func<object,object>返回,而无需额外的演员和调用?或者是否有其他更有效的方法来调用具有对象类型作为实例类型的object.attribute getter?

2 个答案:

答案 0 :(得分:1)

  

有没有办法将getter(get_Id)作为一个返回   Func没有额外演员和电话?

C#不会让你在没有演员的情况下调用getter(当然除了dynamic类型,但这是另一个故事),因为这会违反类型安全,C#保证。

但是你可以发出IL代码,它不包含这样的强制转换并直接调用getter。当然,当你超越这样的编译器时,你有责任只在正确类型的对象上调用这个方法,否则你的应用程序可能会崩溃。

以下代码显示了如何发出此类IL代码。我的测量结果表明,在 Release 模式下,发出的委托是约。比编译的lambda快3倍(在 Debug 模式下,它似乎实际上有点慢)。

public static Func<object, object> EmitUntypedGetter(PropertyInfo pi)
{
    DynamicMethod method = new DynamicMethod(
        "PropertyGetter",
        typeof(Object),
        new[] { typeof(Object) },
        Assembly.GetExecutingAssembly().ManifestModule);

    ILGenerator il = method.GetILGenerator(100);

    // Load object onto the stack.
    il.Emit(OpCodes.Ldarg_0);

    // Call property getter
    il.EmitCall(OpCodes.Callvirt, pi.GetGetMethod(), null);

    // If property returns value-type, value must be boxed
    if(pi.PropertyType.IsValueType)
        il.Emit(OpCodes.Box, pi.PropertyType);

    // Exit method
    il.Emit(OpCodes.Ret);

    return (Func<Object, Object>)method.CreateDelegate(typeof(Func<Object, Object>));
}

<小时/>

编辑:

在我的计算机上,发出的代码的性能始终比lambda版本快得多。

在Windows 10 Home,CPU Intel Core2 Q9400上进行测试,使用Visual Studio 2017 CE v.15.4.0编译,控制台应用程序面向.NET Framework 4.7,发布模式(项目属性中的优化代码选项)已启用),在Visual Studio外部执行(附加VS,禁用某些优化)

我的结果:

Compiled lambda (value type)     : 40827 ms
Compiled lambda (reference type) : 37558 ms
Emit (value type)                : 16963 ms
Emit (reference type)            : 11903 ms

用于测试的程序:

public struct MyClass
{
    public int I => 42;
    public string S => "foo";
}

public static void Main()
{
    var valueTypeProperty = typeof(MyClass).GetProperty("I");
    var referenceTypeProperty = typeof(MyClass).GetProperty("S");

    var lambdaValueTypeGetterDelegate = BuildUntypedGetter(valueTypeProperty);
    var lambdaReferenceTypeGetterDelegate = BuildUntypedGetter(referenceTypeProperty);
    var emitValueTypeGetterDelegate = EmitUntypedGetter(valueTypeProperty);
    var emitReferenceTypeGetterDelegate = EmitUntypedGetter(referenceTypeProperty);

    //warm-up - ensures that delegates are properly jitted
    lambdaValueTypeGetterDelegate(new MyClass());
    lambdaReferenceTypeGetterDelegate(new MyClass());
    emitValueTypeGetterDelegate(new MyClass());
    emitReferenceTypeGetterDelegate(new MyClass());

    TestDelegate("Compiled lambda (value type)     ", lambdaValueTypeGetterDelegate);
    TestDelegate("Compiled lambda (reference type) ", lambdaReferenceTypeGetterDelegate);
    TestDelegate("Emit (value type)                ", emitValueTypeGetterDelegate);
    TestDelegate("Emit (reference type)            ", emitReferenceTypeGetterDelegate);

    Console.ReadLine();
}

private static void TestDelegate(string description, Func<object, object> getterDelegate)
{
    const long LOOPS_COUNT = 1_000_000_000;
    var obj = new MyClass();

    Stopwatch sw = new Stopwatch();
    sw.Start();

    for (long i = 0; i < LOOPS_COUNT; i++)
    {
        getterDelegate(obj);
    }

    sw.Stop();

    Console.WriteLine($"{description}: {sw.ElapsedMilliseconds} ms");
}

答案 1 :(得分:1)

  

有没有办法只需将getter(get_Id)作为Func返回,而无需额外的强制转换和调用?

不符合您的要求。虽然通常可以将委托直接动态绑定到方法(或属性访问器),但委托类型必须与要绑定的方法签名兼容。对你来说情况并非如此。

考虑这个例子:

class MyClass {
    string Id { get; set; }
}

此处,以Func<,>表示的访问者将为Func<MyClass, String>。但是,您需要Func<object, object>。您正在更改参数类型和返回类型。只有在某些限制范围内才允许这样做。

类型Func<-T, +TReturn>在其输入类型T上是逆变,在其输出类型TReturn上是协变。因此,当Func<,>的目标类型更具体T 更不具体时,TReturn实例可转换为其他实例化。例如:

  

Func<object, *>可转换为Func<string, *>,因为接收object的函数总是可以接收string。反之则不然。

     

Func<*, string>可转换为Func<*, object>,因为返回string的函数始终返回object。再说一遍,事实并非如此。

在C#中,严格执行这些规则。当泛型参数仅作为输出出现时,它可以是协变的;当它仅作为输入出现时或逆变。此外,此差异仅适用于参考类型,这意味着Func<*, int>无法分配给Func<*, object>

我担心没有办法绕过它:绑定到Func<object, object>的任何方法都必须接受object参数,这意味着您需要一个向下转换为源类型的中间方法。它还需要明确地设置原始返回值。虽然你可以通过直接发射IL消除一些转换,正如@Ňuf所示,我认为你不会看到有意义的影响。

但是,根据您使用的值如何使用,可能会有更好的方法。不是使用生成的代码将值拉入可以对其进行操作的C#Universe,而是执行相反的操作:将该逻辑推入生成的代码中,您可以根据您正在使用的类型进行专门化。或者,或者,使该逻辑通用,并使用运行时代码生成分派到正确的通用实例。