比反射更快:委托 第2部分

目录

构造函数

静态方法

实例方法

总结


 GitHubNuget包中提供了具有新功能和错误修复的代码。

现在是时候介绍以下成员了。(如想了解字段、属性等情况,可查看上一篇文章)

  1. 静态的
    1. 方法
    1. 方法
    2. 构造函数 

我们将讨论的第一个成员是构造函数。 

构造函数

当然,我们谈论的是非静态构造函数。是的,静态的也存在,但它们是初始化类型而不是对象的特殊方法。它们在首次使用类型时自动调用,我们自己不需要这样做,我们当然不需要委托。

任何类型都可以包含任意数量的构造函数,因此我们首先需要一种方法来指示我们需要在委托中调用哪一个。由于构造函数可以根据参数类型而不同,因此我们可以使用与索引器类似的方式执行此操作——参数的类型集清楚地指示应该是哪一个。当然,类型通常没有任何特殊的构造函数(由用户声明)。在这种情况下,我们将查找默认构造函数,不带参数。由于这种情况不太复杂,我们将首先讨论这个问题。

我们需要开始什么?由于每个类型至少有一个构造函数,即使它没有明确定义,我们也不需要向添加任何代码到TestClass(来自上一篇文章)里面。但是,和以前的其他成员一样,我们必须添加单独的方法来通过反射来获取构造函数。

private static ConstructorInfo GetConstructorInfo(Type source, Type[] types)
{
    return (source.GetConstructor(BindingFlags.Public, null, types, null) ??
              source.GetConstructor(BindingFlags.NonPublic, null, types, null)) ??
              source.GetConstructor(
                BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null,
                types, null);
}

在上一篇文章中针对其他类型的成员详细解释了上述代码背后的逻辑。快速解释:它查找public,然后是privateprotected以及最后的内部构造函数。

现在我们可以实现一个方法,它将为默认构造函数创建委托。这个在DelegateFactory中的新方法将被调用DefaultConstructor

public static Func<TSource> DefaultContructor<TSource>()
{
    var source = typeof(TSource);
    var constructorInfo = GetConstructorInfo(source, Type.EmptyTypes);
    if (constructorInfo == null)
    {
        return null;
    }
    return (Func<TSource>)Expression.Lambda(Expression.New(constructorInfo)).Compile();
}

正如您所看到的,它与前一篇文章中的PropertyGet方法非常相似。首先,从类型获取构造函数,然后在null检查之后将其传递给创建调用此构造函数的表达式的Expression.New方法。之后,返回值。

应按以下方式调用上述方法。

var dc = DelegateFactory.DefaultContructor<TestClass>();
var instance = dc();

它的工作方式与任何其他方法不同,它不接受参数并返回值,其在本例中是我们的新实例。

当我们无法以这种方式访问时会怎么样呢?我们可以编写返回Type对象的类型的扩展方法。

public static Func<object> DefaultContructor(this Type source)
{
    var constructorInfo = GetConstructorInfo(source, Type.EmptyTypes);
    if (constructorInfo == null)
    {
        return null;
    }
    return (Func<object>)Expression.Lambda(Expression.New(constructorInfo)).Compile();
}<br /><br />var dc3 = Type.DefaultContructor()

生成的委托以完全相同的方式调用。如果你想知道为什么没有对对象的返回值的转换,那是因为源类型总是一个类类型——结构不能有无参数构造函数。您可以通过获取int类型的构造函数列表来轻松测试它——即反射没有返回任何构造函数。

上述两种方法都创建了可以转换为以下lambda的委托。

Func<TestClass> d = () => new TestClass();

现在我们应该讨论带有参数的更复杂的构造函数。让我们在TestClass类上创建一些测试构造函数。

internal TestClass(int p)
    : this()
{
}

protected TestClass(bool p)
    : this()
{
}

private TestClass(string p)
    : this()
{
}

理想情况下,将使用源类型参数(或具有源类型的参数)调用新重​​载,并使用表示构造函数参数集合的类型列表调用参数。

Func<int,TestClass> c = DelegateFactory.Contructor<TestClass, int>>();

上面的问题是,C#不是C ++,你不能拥有带有未指定数量的类型参数的类。因此,要么我们可以为每个参数创建单独的重载(达到Func 类的上限),要么写一些不太方便的重载,但更通用。

var c = DelegateFactory.Contructor<TestClass, Func<int, TestClass>>();

返回的变量是一样的。在前面的示例中,我们也可以编写var关键字而不是显式类型的委托,但是为什么我们不能这样做,因为它的可读性会降低。第一个和第二个版本之间的区别在于,第二个版本传递了显式返回类型。那么我们如何才能获得参数类型呢?反射非常容易。

private static Type[] GetFuncDelegateArguments<TDelegate>() where TDelegate : class
{
    return typeof(TDelegate).GenericTypeArguments.Reverse().Skip(1).Reverse().ToArray();
}

上面的函数从Func类表示的委托中检索参数。执行一个类型参数的反转和跳过以省略最后一个,这始终是返回类型。对于上面的示例性调用(Func<TestClass,int>),它将返回{typeof(int)}具有单个元素的数组——整数类型,Int32

如果你看起来足够熟悉代码,你会注意到,该TestClass 类型参数被传递两次:它自己和返回类型的构造函数委托。没有必要,我们可以通过从传递的委托类型获取具有已定义构造函数中的源类型来删除第一个。通过以下方法可以非常容易地完成。

private static Type GetFuncDelegateReturnType<TDelegate>() where TDelegate : class
{
    return typeof(TDelegate).GenericTypeArguments.Last();
}

现在我们可以为带参数的构造函数实现第一次重载。

public static TDelegate Contructor<TDelegate>() where TDelegate : class
{
    var source = GetFuncDelegateReturnType<TDelegate>();
    var ctrArgs = GetFuncDelegateArguments<TDelegate>();
    var constructorInfo = GetConstructorInfo(source, ctrArgs);
    if (constructorInfo == null)
    {
        return null;
    }
    var parameters = ctrArgs.Select(Expression.Parameter).ToList();
    return Expression.Lambda(Expression.New(constructorInfo, parameters), parameters).Compile() as TDelegate;
}

显然,TDelegate 类型中的类型参数集必须正确才能工作,但是当您使用反射时它是完全相同的——您必须传递正确的类型以获取MethodInfo 然后更正参数以调用方法。

例如,对于Constructor方法上面带有int参数的TestClass构造函数,将创建委托,可以由下面的lambda表示。

Func<int, TestClass> d = i => new TestClass(i);

DefaultContructor 方法的主要区别在于:Expression.NewExpression.Lambda 调用中的参数集,从TDelegate作为类型参数传入的类型数组创建。

关于该机制的好处是,它对于无参数构造函数也可以工作,我们可以使用这个新方法来重写DefaultContructor方法。

public static Func<TSource> DefaultContructor<TSource>()
{
    return Contructor<Func<TSource>>();
}

这应该返回没有返回实例的参数的委托,因此Func<TSource>作为委托类型传递。但是,当我们不知道源类型并且我们想要一个带参数的构造函数的委托时,情况怎么样?我们必须更改Constructor方法,以便返回一个带有返回object类型的委托。因此,我们无法从委托中获取源类型,因此我们必须以不同方式传递它。

public static TDelegate Contructor<TDelegate>(this Type source)
    where TDelegate : class
{
    var ctrArgs = GetFuncDelegateArguments<TDelegate>();
    var constructorInfo = GetConstructorInfo(source, ctrArgs);
    if (constructorInfo == null)
    {
        return null;
    }
    var parameters = ctrArgs.Select(Expression.Parameter).ToList();
    Expression returnExpression = Expression.New(constructorInfo, parameters);
    if (!source.IsClass)
    {
        returnExpression = Expression.Convert(returnExpression, typeof(object));
    }
    return Expression.Lambda(returnExpression, parameters).Compile() as TDelegate;
}

有几点可能需要澄清。它与前一个几乎相同,但它确实返回一个对象而不是源类型实例。如果要构造的类型是值类型,则在返回最终值之前将其转换为对象。最后安全转换,强制用户传递匹配的委托。

如果我们写了这样的结构:

public struct TestStruct
{
    public TestStruct(int i)
    {

    }
}

调用上面的方法会产生非常类似于下面的委托。

var c = typeof(TestStruct).Contructor<Func<int, object>>();
Func<int, object> d = i => (object)new TestStruct(i);

当我们无法访问所有类型的参数时,会出现下一个问题——未知类型方法可能具有未知或不可用的参数。这是一个类似的问题,迫使我们创建Constructor<TDelegate>重载——如果我们不知道参数的类型,我们真的不知道应该构造什么类型的委托。当然,我们可以创建一个方法,在构造函数中返回具有正确数量参数的委托,除非将未知类型更改为object。但它仍限制我们在Func 类中允许的参数数量(但是如果代码具有包含许多参数的方法,那么可能存在比这更重要的问题),并且它可以返回与预期不同的构造函数(可以有多个具有给定数量的参数)。因此,我们应该编写更通用的方法来获取构造函数的类型数组,具体如何使用上一篇文章中的索引器来完成。

public static Func<object[], object> Contructor(this Type source, params Type[] ctrArgs)
{
    var constructorInfo = GetConstructorInfo(source, ctrArgs);
    if (constructorInfo == null)
    {
        return null;
    }
    var argsArray = Expression.Parameter(typeof(object[]));
    var paramsExpression = new Expression[ctrArgs.Length];
    for (var i = 0; i < ctrArgs.Length; i++)
    {
        var argType = ctrArgs[i];
        paramsExpression[i] = 
            Expression.Convert(Expression.ArrayIndex(argsArray, Expression.Constant(i)), argType);
    }
    Expression returnExpression = Expression.New(constructorInfo, paramsExpression);
    if (!source.IsClass)
    {
        returnExpression = Expression.Convert(returnExpression, typeof(object));
    }
    return (Func<object[], object>)Expression.Lambda(returnExpression, argsArray).Compile();
}

它返回获取对象数组并返回对象的委托。对象在同一索引处转换为正确的参数类型。然后将处理参数的集合用作构造函数的参数集合。如果是值类型,则创建的实例将转换为对象。作为具有单个参数的构造函数的lambda,这将更加清晰。

Func<object[], object> d = args => (object)new TestStruct((int)args[0]);

我们现在应该测试这些方法。下面的行创建委托,然后通过这些委托创建实例。

var cd = DelegateFactory.DefaultContructor<TestClass>();
var cd1 = Type.DefaultContructor();
var cd2 = Type.Contructor<Func<object>>();

var t_ = cd();
var t1 = cd1();
var t2 = cd2();

var c1 = DelegateFactory.Contructor<Func<TestClass>>();
var c2 = DelegateFactory.Contructor<Func<int, TestClass>>();
var c3 = DelegateFactory.Contructor<Func<bool, TestClass>>();
var c4 = DelegateFactory.Contructor<Func<string, TestClass>>();
var c5 = DelegateFactory.Contructor<Func<int, TestClass>>();
var c6 = Type.Contructor(typeof(int));
var c7 = typeof(TestStruct).Contructor<Func<int, object>>();
var c8 = typeof(TestStruct).Contructor(typeof(int));

var t3 = c1();
var t4 = c2(0);
var t5 = c3(false);
var t6 = c4("");
var t7 = c5(0);
var t8 = c6(new object[] { 0 });
var t9 = c7(0);
var t10 = c8(new object[] { 0 });

如果您想知道上面的Type变量是什么——它是TestClass实例,它是用于测试所有委托的类。两者都在前一篇文章中进行了解释。您还可以在github查找本文(以及前一篇)的代码以获取更多信息。

名称以“t”开头的所有变量都将具有新创建的实例的值。通过委托c1c2c3c4调用的构造函数具有不同的可见性(public, internal, protectedprivate),并且它们都可以毫无问题地工作。它能用,但有多快?

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = new TestClass();
}
_stopWatch.Stop();
Console.WriteLine("Public default constructor directly: {0}", _stopWatch.ElapsedMilliseconds);

var constructorInfo = Type.GetConstructor(Type.EmptyTypes);
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = (TestClass)constructorInfo.Invoke(null);
}
_stopWatch.Stop();
Console.WriteLine("Public default constructor via reflection: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = Activator.CreateInstance();
}
_stopWatch.Stop();
Console.WriteLine("Public default constructor via Activator: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var t = c1();
}
_stopWatch.Stop();
Console.WriteLine("Public default constructor proxy: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = c5(0);
}
_stopWatch.Stop();
Console.WriteLine("Public constructor with parameter proxy: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    c6(new object[] {0});
}
_stopWatch.Stop();
Console.WriteLine("Public constructor with parameter via array proxy: {0}", _stopWatch.ElapsedMilliseconds);

运行此操作后,我们将看到如下结果。

Public default constructor directly: 14896
Public default constructor via reflection: 33763
Public default constructor via Activator: 21690
Public default constructor proxy: 14923
Public constructor with parameter proxy: 15261
Public constructor with parameter via array proxy: 16768

正如您所看到的,直接调用默认构造函数和委托之间的差异非常小,因为它是在没有任何中间调用的情况下调用相同的方法。因为Activator类中的代码在类型中搜索正确的构造函数(通过给定CreateInstance方法的参数)然后调用它,所以Activator的速度明显较慢。如果这么做很多次就需要时间。反射速度慢了两倍多。最后两个委托的速度有点慢,但不是那么多(3-12%)。空的或不太复杂的构造函数(TestClass构造函数具有字段和属性的初始化)的差异会更大,但即使这样,它只涉及较少的计算(与反射或者Activator相比),只需要很少的强制转换和索引操作。

现在,当我们覆盖构造函数时,我们可以切换到更重要的成员类型:方法。 

静态方法

第一篇文章中介绍了属性之后,是时候介绍方法的了,这也是最常用的成员类型之一。通过处理构造函数获得的经验,方法应该不是问题。首先是静态方法。

静态方法和构造函数之间的主要区别在于构造函数具有固定的返回类型,它与源类型相同。使用静态方法我们必须更灵活,因此我们需要一个额外的参数用于所有委托创建方法。与构造函数类似,我们将为静态方法编写三个重载:类型参数中具有源类型的静态方法,类型参数中具有委托类型的Type扩展方法,以及具有所需静态方法重载的参数列表的Type扩展方法(如果有多个同名的方法)。代码形式会更加清晰。

var sm1 = DelegateFactory.StaticMethod<TestClass, Func<string, string>>("StaticPrivateMethod");
var sm2 = Type.StaticMethod<Func<string, string>>("StaticPublicMethod");
var sm3 = Type.StaticMethod("StaticPublicMethod", typeof(string));

这三个重载应该涵盖所有情况:当我们知道所有类型(源类类型,参数类型和方法的返回类型)时,我们不知道源类型,但我们知道参数类型,当我们不知道任何类型。

有一个并发症(与构造函数相比):方法,静态和实例,根本不必具有返回类型(或者更精确的返回类型等于typeof(void))。所以第一次和第二次重载也应该使用Action委托一起工作。

var sm1 = DelegateFactory.StaticMethod<TestClass, Action<string, string>>("StaticPublicMethod");
var sm2 = Type.StaticMethod<Action<string, string>>("StaticPublicMethod");

第三次重载是更复杂的情况。如果它会产生不同的委托(ActionFunc),如果静态方法是void或不是void,那就太棒了。这是可能的,但不是很方便。看下面的代码。

var sm1 = (Action<object[]>)Type.StaticMethod("StaticPublicMethod", typeof(string));
var sm2 = (Func<object[], object>)Type.StaticMethod("StaticPublicMethod", typeof(string));

为了使生成的委托可以调用,我们必须将它强制转换为正确的类型,因此无论如何都需要将有关委托类型的信息嵌入到代码中。事实是,要使用任何类的任何静态方法,我们必须知道它的签名,以便能够编写使用此方法的代码。因此,当我们不知道源和参数(在编译时)、void和非void方法的类型时,编写两个单独的方法来创建静态方法委托会更方便。

var sm1 = Type.StaticMethod("StaticPublicMethod", typeof(string));
var sm2 = Type.StaticMethodVoid("StaticPublicMethodVoid", typeof(string));

第一个委托用于Func ,第二委托用于Action委托。如果DelegateFactory用户必须知道静态方法签名,这样使用起来会更方便。

在实现上面说的之前,我们必须编写一个获取静态方法MethodInfo对象的方法,就像构造函数一样。

private static MethodInfo GetStaticMethodInfo(Type source, string name, Type[] types)
{
    var methodInfo = (source.GetMethod(name, BindingFlags.Static, null, types, null) ??
                               source.GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic, null, types, null)) ??
                               source.GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, null, types, null);
    return methodInfo;
}

与以前的成员类型非常相似的代码。我们正在寻找具有指定名称的方法,其中标志设置为静态成员,并且方法的可见性不同。

现在我们可以实现StaticMethod重载。

public static TDelegate StaticMethod<TSource, TDelegate>(string name)
    where TDelegate : class
{
    return typeof(TSource).StaticMethod<TDelegate>(name);
}

public static TDelegate StaticMethod<TDelegate>(this Type source,
    string name)
    where TDelegate : class
{

    var paramsTypes = GetFuncDelegateArguments<TDelegate>();
    var methodInfo = GetStaticMethodInfo(source, name, paramsTypes);
    return methodInfo.CreateDelegate(typeof(TDelegate)) as TDelegate;
}

这里没什么复杂的。只需从MethodInfo对象委托创建,例如在属性,索引器和构造函数中。第一个重载只是调用第二个重载。当然TDelegate必须与静态方法签名兼容。如果要通过反射调用方法,则需要相同的操作。

如果我们添加新的测试方法到TestClass中,由上述StaticMethod方法创建的委托将类似于以下lambda语句。

public static string StaticPublicMethod(string s)
{
    return s;
}

Func<string, string> d = s1 => TestClass.StaticPublicMethod(s1);

StaticMethodStaticMethodVoid形式的第三种重载稍微复杂一些,与本文中的对象和前一个中的其他成员调用的构造函数一样,它需要表达式才能工作。

public static Func<object[], object> StaticMethod(this Type source,
    string name, params Type[] paramsTypes)
{
    var methodInfo = GetStaticMethodInfo(source, name, paramsTypes);
    var argsArray = Expression.Parameter(typeof(object[]));
    var paramsExpression = new Expression[paramsTypes.Length];
    for (var i = 0; i < paramsTypes.Length; i++)
    {
        var argType = paramsTypes[i];
        paramsExpression[i] =
            Expression.Convert(Expression.ArrayIndex(argsArray, Expression.Constant(i)), argType);
    }
    Expression returnExpression = Expression.Call(methodInfo, paramsExpression);
    if (!source.IsClass)
    {
        returnExpression = Expression.Convert(returnExpression, typeof(object));
    }
    return (Func<object[], object>)Expression.Lambda(returnExpression, argsArray).Compile();
}

public static Action<object[]> StaticMethodVoid(this Type source,
    string name, params Type[] paramsTypes)
{
    var methodInfo = GetStaticMethodInfo(source, name, paramsTypes);
    var argsArray = Expression.Parameter(typeof(object[]));
    var paramsExpression = new Expression[paramsTypes.Length];
    for (var i = 0; i < paramsTypes.Length; i++)
    {
        var argType = paramsTypes[i];
        paramsExpression[i] =
            Expression.Convert(Expression.ArrayIndex(argsArray, Expression.Constant(i)), argType);
    }
    Expression returnExpression = Expression.Call(methodInfo, paramsExpression);
    return (Action<object[]>)Expression.Lambda(returnExpression, argsArray).Compile();
}

如果您想知道这些方法是否可以重写以共享代码,那么您是对的。它们可以并且不会损失性能,例如属性设置器。我们可以检查方法的返回类型是否为void并且省略了对对象的转换,最后,将委托从类型参数强制转换为返回类型。

public static Func<object[], object> StaticMethod(this Type source,
    string name, params Type[] paramsTypes)
{
    return StaticMethod<Func<object[], object>>(source, name, paramsTypes);
}

public static Action<object[]> StaticMethodVoid(this Type source,
    string name, params Type[] paramsTypes)
{
    return StaticMethod<Action<object[]>>(source, name, paramsTypes);
}

public static TDelegate StaticMethod<TDelegate>(this Type source,
    string name, params Type[] paramsTypes)
    where TDelegate : class
{
    var methodInfo = GetStaticMethodInfo(source, name, paramsTypes);
    var argsArray = Expression.Parameter(typeof(object[]));
    var paramsExpression = new Expression[paramsTypes.Length];
    for (var i = 0; i < paramsTypes.Length; i++)
    {
        var argType = paramsTypes[i];
        paramsExpression[i] =
            Expression.Convert(Expression.ArrayIndex(argsArray, Expression.Constant(i)), argType);
    }
    Expression returnExpression = Expression.Call(methodInfo, paramsExpression);
    if (methodInfo.ReturnType != typeof(void) && !methodInfo.ReturnType.IsClass)
    {
        returnExpression = Expression.Convert(returnExpression, typeof(object));
    }
    return Expression.Lambda(returnExpression, argsArray).Compile() as TDelegate;
}

我们应该在TestClass中创建一些新方法以便能够测试一切是否正常。

public static string StaticPublicMethod(int i)
{
    return i.ToString();
}

public static string StaticPublicMethodVoidParameter;

public static void StaticPublicMethodVoid(string s)
{
    StaticPublicMethodVoidParameter = s;
}

internal static string StaticInternalMethod(string s)
{
    return s;
}

protected static string StaticProtectedMethod(string s)
{
    return s;
}

private static string StaticPrivateMethod(string s)
{
    return s;
}

对于StaticPublicMethodStaticPublicMethodVoid 创建的委托可以由lambdas表示,如下所示。

Func<object[], object> d1 = os => TestClass.StaticPublicMethod((string)os[0]);
Action<object[]> d2 = os => TestClass.StaticPublicMethodVoid((string)os[0]);

如果返回StaticPublicMethodvalue类型为值类型,则lambda返回的值也将转换为object

现在我们可以测试在控制台应用程序中是否一切正常。让我们在程序的Main方法中添加几行。

var sm1 = DelegateFactory.StaticMethod<TestClass, Func<string, string>>("StaticPublicMethod");
var sm2 = DelegateFactory.StaticMethod<TestClass, Func<string, string>>("StaticInternalMethod");
var sm3 = DelegateFactory.StaticMethod<TestClass, Func<string, string>>("StaticProtectedMethod");
var sm4 = DelegateFactory.StaticMethod<TestClass, Func<string, string>>("StaticPrivateMethod");
var sm5 = Type.StaticMethod<Func<string, string>>("StaticPublicMethod");
var sm6 = Type.StaticMethod<Func<string, string>>("StaticInternalMethod");
var sm7 = Type.StaticMethod<Func<string, string>>("StaticProtectedMethod");
var sm8 = Type.StaticMethod<Func<string, string>>("StaticPrivateMethod");
var sm9 = Type.StaticMethod("StaticPublicMethod", typeof(string));
var sm10 = Type.StaticMethodVoid("StaticPublicMethodVoid", typeof(string));
var sm11 = Type.StaticMethod("StaticInternalMethod", typeof(string));
var sm12 = Type.StaticMethod("StaticProtectedMethod", typeof(string));
var sm13 = Type.StaticMethod("StaticPrivateMethod", typeof(string));
var sm14 = DelegateFactory.StaticMethod<TestClass, Func<int, int>>("StaticPublicMethodValue");
var sm15 = Type.StaticMethod<Func<int, int>>("StaticPublicMethodValue");
var sm16 = Type.StaticMethod("StaticPublicMethodValue", typeof(int));
            
var t = sm1("test");
var t2 = sm2("test");
var t3 = sm3("test");
var t4 = sm4("test");
var t5 = sm5("test");
var t6 = sm6("test");
var t7 = sm7("test");
var t8 = sm8("test");
var t9 = sm9(new object[] { "test" });
sm10(new object[] { "test" });
var t10 = TestClass.StaticPublicMethodVoidParameter;
var t11 = sm11(new object[] { "test" });
var t12 = sm12(new object[] { "test" });
var t13 = sm13(new object[] { "test" });
var t14 = sm14(0);
var t15 = sm15(0);
var t16 = sm16(new object[] { 0 });

很多行后面都有非常简单的逻辑。开始的四行通过带参数类型的StaticMethod方法重载来创建委托,因此创建的委托具有方法签名的全部知识。接下来的四行使用Type实例中的源类型生成委托——同样保留静态方法的签名。之后,我们有两行,它们接受一组参数类型,然后创建具有单个参数的委托,无论目标静态方法的参数数量如何。它们的区别仅在于创建的委托的返回类型(voidobject)。接下来的三个以相同的方式工作,但对于非公共方法。最后三个测试全部StaticMethod重载,但对于返回值类型而不是引用类型的方法。上面测试的第二部分只是调用创建的委托来确保它们有效。下图显示了这些调用返回的值。

根据TestClass中的静态方法的定义,返回的值应该是什么。

关于静态方法的最后一件事是测试委托的性能。

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = TestClass.StaticPublicMethod("test");
}
_stopWatch.Stop();
Console.WriteLine("Static Public method directly: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = sm1("test");
}
_stopWatch.Stop();
Console.WriteLine("Static Public method proxy: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = sm9(new object[] { "test" });
}
_stopWatch.Stop();
Console.WriteLine("Static Public method via proxy with array: {0}", _stopWatch.ElapsedMilliseconds);

var methodInfo = Type.GetMethod("StaticPublicMethod", new[] { typeof(string) });
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = methodInfo.Invoke(null, new object[] { "test" });
}
_stopWatch.Stop();
Console.WriteLine("Static Public method via reflection: {0}", _stopWatch.ElapsedMilliseconds);

控制台应用程序的结果再次证明委托比反射快得多。

Static Public method directly: 1160
Static Public method proxy: 1167
Static Public method via proxy with array: 2930
Static Public method via reflection: 21317

正如你所看到的,委托调用方法只是慢了一点。更彻底的测试表明,它可能会因方法签名和正文而有所不同,但仍然应该只减慢几个百分点。对象数组的调用显然较慢,因为在所有的转换和索引中在上面的示例中,它需要2.5倍的时间,但对于比单个return语句更复杂的方法,它应该更少。最后一行显示反射比预期慢得多(大约18次)。

最后要做的是添加检查methodInfo变量是否为null以防止错误。它与构造函数的工作方式相同:null传播运算符?用于仅具有反射的重载和用于表达式重载的if语句。

现在我们可以跳转到实例方法了。 

实例方法

实例方法将具有非常相似的重载,除了StaticMethod<TSource, TDelegate>(string name), 这是不必要的,因为我们可以从TDelegate(类似于属性)的第一个参数获得TSource实例方法。实例方法就像静态方法,它使用目标实例获取第一个参数,这为方法体内的this 关键字赋予了意义。理解它的最简单方法是将实例方法视为扩展方法。例如下面的测试方法:

public string PublicMethod(string s)
{
    return s;
}

我们可以创建看起来像静态扩展方法的委托。

public string Delegate(this TestClass @this, string s)
{
    return @this.PublicMethod(s);
}

它的工作方式与上一篇文章中的实例属性,字段和索引器相同。

好的,让我们实现InstanceMethod方法的重载。首先,将是已知所有类型(源,参数和返回类型)的一个。

public static TDelegate InstanceMethod<TDelegate>(string name)
    where TDelegate : class
{
    var paramsTypes = GetFuncDelegateArguments<TDelegate>();
    var source = paramsTypes.First();
    paramsTypes = paramsTypes.Skip(1).ToArray();
    var methodInfo = GetMethodInfo(source, name, paramsTypes);
    return methodInfo?.CreateDelegate(typeof(TDelegate)) as TDelegate;
}

由于我们知道所有类型,我们还必须知道方法的签名,并且此签名作为TDelegate类型传递给InstanceMethod方法。考虑上面提到的PublicMethod。对于此方法TDelegate 应如下所示。

Func<TestClass, string, string>

此方法接受两个参数(实例,应调用哪个方法和目标方法参数)并返回字符串。其余代码与Constructor方法非常相似。我们从TDelegate中获取类型参数,第一个用作源类型,其余用于在源类型中搜索目标方法的正确重载。之后,与其他成员类型一样创建委托。对此重载的示例调用应如下所示。

var m1 = DelegateFactory.InstanceMethod<Func<TestClass, string, string>>("PublicMethod");

对于其他成员类型,我们总是讨论重载,当我们不想将源类型作为类型参数传递时,而是将它作为扩展方法的第一个参数(this)传递。事实上,使用实例方法,我们遇到与构造函数相同的问题:我们不能创建带有未指定类型参数数量的重载,并返回带有未指定参数数量的委托。它强制我们传递委托类型或为每个参数编写单独的方法。传递委托导致源类型被指定两次。如果我们想要编写类似的扩展方法,就像使用其他成员一样,我们应该允许委托使用第一个参数作为object而不是源类型。这使事情变得更复杂,但它会使DelegateFactory更简洁。

public static TDelegate InstanceMethod<TDelegate>(this Type source, string name)
    where TDelegate : class
{
    var delegateParams = GetFuncDelegateArguments<TDelegate>(true);
    var methodInfo = GetMethodInfo(source, name, delegateParams);
    if (methodInfo == null)
    {
        return null;
    }
    Delegate deleg;
    if (delegateParams[0] == source)
    {
        deleg = methodInfo.CreateDelegate(typeof(TDelegate));
    }
    else
    {
        var sourceParameter = Expression.Parameter(typeof(object));
        var expressions = delegateParams.Select(Expression.Parameter).ToArray();
        Expression returnExpression = Expression.Call(Expression.Convert(sourceParameter, source),
            methodInfo, expressions.Cast<Expression>());
        if (methodInfo.ReturnType != typeof(void) && !methodInfo.ReturnType.IsClass)
        {
            returnExpression = Expression.Convert(returnExpression, typeof(object));
        }
        var lamdaParams = new[] { sourceParameter }.Concat(expressions);
        deleg = Expression.Lambda(returnExpression, lamdaParams).Compile();
    }
    return deleg as TDelegate;
}

有很多代码,但并不是那么复杂。如果委托(源实例,对象或任何其他兼容类型)的第一个参数是和source参数相等的,则它与先前的重载完全相同。如果没有,它通过表达式执行委托创建,非常类似于相应的Constructor重载。从TDelegate 返回的参数类型列表用于为Expression.Call创建ExpressionsParameter集合。相同的集合(源参数为对象)用作Expression.Lambda的参数集。If语句仅当对象是结构且目标方法不返回void时才将返回值强制转换为对象。

对于TestClass.PublicMethod方法,上面的重载将创建类似于下面的lambdas的委托。

Func<TestClass, string, string> d1 = (i, s) => i.PublicMethod(s);
Func<object, string, string> d2 = (i, s) => ((TestClass)i).PublicMethod(s);

如果我们使用具有正确实例类型的委托类型或仅使用对象来调用它,则有两个版本。

现在,我们可以讨论第三个重载,其只接受对象(在两个版本中:对于void和非void方法)与静态方法完全相同。

public static TDelegate InstanceMethod<TDelegate>(this Type source,
    string name, params Type[] paramsTypes)
    where TDelegate : class
{
    var methodInfo = GetMethodInfo(source, name, paramsTypes);
    if (methodInfo == null)
    {
        return null;
    }
    var argsArray = Expression.Parameter(typeof(object[]));
    var sourceParameter = Expression.Parameter(typeof(object));
    var paramsExpression = new Expression[paramsTypes.Length];
    for (var i = 0; i < paramsTypes.Length; i++)
    {
        var argType = paramsTypes[i];
        paramsExpression[i] =
            Expression.Convert(Expression.ArrayIndex(argsArray, Expression.Constant(i)), argType);
    }
    Expression returnExpression = Expression.Call(Expression.Convert(sourceParameter, source),
        methodInfo, paramsExpression);
    if (methodInfo.ReturnType != typeof(void) && !methodInfo.ReturnType.IsClass)
    {
        returnExpression = Expression.Convert(returnExpression, typeof(object));
    }
    return Expression.Lambda(returnExpression, sourceParameter, argsArray).Compile() as TDelegate;
}

这种重载与静态方法的重载几乎相同。唯一不同的是一个带有实例的参数应该调用哪个方法。除此之外它是相同的:获取方法信息,检查null,将params数组转换为其类型,调用目标方法,如果需要,转换返回值。因为TestClass.PublicMethod 它将创建类似于下面的lambda的委托。

Func<object, object[], object> d = (o, p) => ((TestClass)o).PublicMethod((string)p[0]);

编写完所有方法后,我们可以测试一切是否正常并检查此解决方案的性能。和以前一样,与其他成员一样,我们可以为方法的所有可见性(公共,内部,受保护和私人)做到这一点。

public string PublicMethodVoidParameter;

public string PublicMethod(string s)
{
    return s;
}

public void PublicMethodVoid(string s)
{
    PublicMethodVoidParameter = s;
}

internal string InternalMethod(string s)
{
    return s;
}
protected string ProtectedMethod(string s)
{
    return s;
}

private string PrivateMethod(string s)
{
    return s;
}

测试与静态方法的测试非常相似。

var m1 = DelegateFactory.InstanceMethod<Func<TestClass, string, string>>("PublicMethod");
var m2 = DelegateFactory.InstanceMethod<Func<TestClass, string, string>>("InternalMethod");
var m3 = DelegateFactory.InstanceMethod<Func<TestClass, string, string>>("ProtectedMethod");
var m4 = DelegateFactory.InstanceMethod<Func<TestClass, string, string>>("PrivateMethod");
var m5 = Type.InstanceMethod<Func<TestClass, string, string>>("PublicMethod");
var m6 = Type.InstanceMethod<Func<object, string, string>>("PublicMethod");
var m7 = Type.InstanceMethod("PublicMethod", typeof(string));
var m8 = Type.InstanceMethodVoid("PublicMethodVoid", typeof(string));

var t = m1(TestInstance, "test");
var t2 = m2(TestInstance, "test");
var t3 = m3(TestInstance, "test");
var t4 = m4(TestInstance, "test");
var t5 = m5(TestInstance, "test");
var t6 = m6(TestInstance, "test");
var t7 = m7(TestInstance, new object[] { "test" });
m8(TestInstance, new object[] { "test" });
var t8 = TestInstance.PublicMethodVoidParameter;

上面的前四行测试了具有不同可见性的不同方法。接下来两行,使用正确的实例类型测试相同的重载,使用object测试第二次。最后两行创建委托只包含void和非void方法的对象。

正如您所看到的,每个创建的委托都可以正常工作,并且返回的变量具有预期值。可以调整用于测试委托给静态方法的性能的类似代码,以测试实例的性能。

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = TestInstance.PublicMethod("test");
}
_stopWatch.Stop();
Console.WriteLine("Public method directly: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = m1(TestInstance, "test");
}
_stopWatch.Stop();
Console.WriteLine("Public method proxy: {0}", _stopWatch.ElapsedMilliseconds);

var methodInfo = Type.GetMethod("PublicMethod");
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = methodInfo.Invoke(TestInstance, new object[] { "test" });
}
_stopWatch.Stop();
Console.WriteLine("Public method proxy: {0}", _stopWatch.ElapsedMilliseconds);

控制台输出的结果应该非常像这样:

Public method directly: 1199
Public method proxy: 1287
Public method proxy: 23044

正如您所看到的,实例方法的委托(与其他成员完全一样)只比直接调用方法慢一点。反射是方式,效率更低,大约慢19倍。 

总结

在本文中,我们介绍了为构造函数,静态和实例方法创建委托的方式和原因。我们只需要介绍事件和特殊类型的方法:泛型方法。。静态和实例方法都可以是通用的,并且与事件一起,它们将在下一篇文章中介绍。

文章代码可以在githubNuget包中找到。

上一篇 比反射更快:委托 第1部分

下一篇 比反射更快:委托 第3部分

 

原文地址:https://www.codeproject.com/Articles/1124966/Faster-than-Reflection-Delegates-Part-3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值