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

目录

为什么不用反射?

委托一切

静态属性

获取访问器(get accessors)

设置访问器(set accessors)

改进

属性

改进

索引器

改进

Setters

静态字段

获取静态字段值

设置静态字段值

字段

获取字段值委托

设置字段值委托

改进

总结


为什么不用反射?如果你和它一起工作。

GitHubNuget包提供了所有三篇文章的代码,包括新功能和错误修复。

为什么不用反射?

如果您使用.NETC#(也可能是其他语言),在某些时候,您将不得不编写更高级和通用的代码,即读取类型的所有属性,搜索属性等。或者您可能想要调用一些私有方法或属性?使用框架或库的某种内部机制并不罕见。通常,第一个解决方案是反射。但问题是:反射很慢。当然,如果您需要很少使用Reflection并且它不是应用程序的主要代码,那就足够了。如果在某些时候更频繁地使用它,您可能会尝试对其进行优化。现在是有趣的部分:如何做到这一点?

Reflection的主要问题是它很慢。它比直接调用类型成员要慢得多。如果你通过Reflection做到这一点,一切都会变慢。如果需要创建类型的实例,可能会使用Activator.CreateInstance方法。它比反射快一点,但也比直接调用构造函数慢。对于属性,方法和构造函数,可以使用表达式(表达式静态方法),但表达式和编译它们甚至比反射慢。

当我在本文中寻找一种快速创建类型实例的方法,我在Stack Overflow上找到了这个问题。根据它,创建实例的最快方法是使用通过编译表达式创建的委托。这让我想到了通过委托获取实例和类型成员的一般机制。毕竟,它最近是我的兴趣点,因为我使用PCL项目处理Xamarin应用程序,这些项目具有可移植的.NET Framework,其功能少于完整版。返回不可用的类型成员的快速机制将非常好,并且会以牺牲平台代码为代价(这是一件好事)使PCL代码更大。因为您可以从ReflectionExpressionlambdas创建委托,并且只需要一点开销(因为您已经调用了另一个方法),因此创建用于获取类型(publicprivate)所有成员的委托的机制是一个好主意。这是本文的主题。 

 下面的代码是使用.NET 4.5Visual Studio 2015中编写的,但是可以将大多数代码移植到旧版本。 

委托一切

类型(Type)可以包含以下类型的成员:

  1. 静态的
    1. 属性
    2. 方法
    3. 字段
    4. 事件
    5. 常量字段
  2. 实例
    1. 属性
    2. 方法
    3. 字段
    4. 构造函数
    5. 事件
    6. 索引

我们可以创建委托来检索所有这些成员。坦率地说,并非所有委托都有意义。例如,委托常量。这些字段不会改变,因此没有必要多次检索它们。您可以使用Reflection轻松检索这些值,并将其保存在某处以备将来使用。

本文不会涉及的唯一内容是静态事件。你有没有用过?说真的,如果绝对需要的话,我想不出一个案例。更常见的是创建普通事件并从单例实例调用它。好处是,只需对实例事件进行少量更改,就可以非常轻松地为静态事件创建委托。 

静态属性

属性和方法是最常见的成员。方法要复杂得多,所以最好从属性开始。

我认为反射最常见的情况是从实例或类型中检索集合或单个属性。它可能用于动态表视图,序列化,反序列化,保存到文件等。静态属性不那么复杂,因此我们将从它开始而不是从实例开始。

获取访问器(get accessors)

我们应该创建一个新项目。它将被称为委托——控制台应用程序,以便于测试。

首先要做的是为测试创建类。我们可以将它命名为TestClass

public class TestClass
{
    public static string StaticPublicProperty { get; set; } = "StaticPublicProperty";

    internal static string StaticInternalProperty { get; set; } = "StaticInternalProperty";

    protected static string StaticProtectedProperty { get; set; } = "StaticProtectedProperty";

    private static string StaticPrivateProperty { get; set; } = "StaticPrivateProperty";
}

第二件事是为委托创建一个类——DelegatesFactory。我们如何检索静态属性?最好的方法是通过Type实例检索它,它表示有关我们的源类型的信息。我们可以使它成为Type类型的扩展方法。这样,它会更方便。我们需要知道我们想要的属性,所以带有属性名称的参数和带有返回类型的类型参数也是一个好主意。

Type.StaticPropertyGet<string>("StaticPublicProperty");

另一种方法是将源类型和属性类型设置为类型参数。这样,我们将使用静态方法而不是扩展方法。此外,由于我们在编译时通常没有类型名称(因为它在运行前不公开或不可用),因此它的可用性较低。

DelegateFactory.StaticPropertyGet<TestClass, string>("StaticPublicProperty");

有了这个假设,我们可以编写控制台应用程序作为DelegateFactory测试。

class ConsoleApplication
{
    private static readonly Type Type = typeof(TestClass);

    static void Main(string[] args)
    {
        var sp = DelegateFactory.StaticPropertyGet<TestClass, string>("StaticPublicProperty");
        var sp1 = Type.StaticPropertyGet<string>("StaticPublicProperty");
    }
}

现在,我们可以编写两种方法的存根。

public static class DelegateFactory
{
    public static Func<TProperty> StaticPropertyGet<TSource, TProperty>(string propertyName)
    {
        return null;
    }

    public static Func<TProperty> StaticPropertyGet<TProperty>(this Type source, string propertyName)
    {
        return null;
    }
}

由于第二种方法(扩展的一个方法)更为通用,我们将首先实现它。要创建委托,我们需要PropertyInfo来获取所请求的属性。是的,我们需要反射。问题是,许多委托需要反射,但这只是一次,用于创建委托。之后,我们可以使用这个委托只需很小的开销,因为程序必须执行两个嵌套方法而不是一个。

通过反射检索属性最重要的部分是我们可以通过这种方式访问PropertyInfo.GetMethod,而GetMethodMethodInfo类型,它将具有CreateDelegate成员。用于创建委托以检索静态公共属性的最简单代码如下所示。

public static Func<TProperty> StaticPropertyGet<TProperty>(this Type source, string propertyName)
{
    var propertyInfo = source.GetProperty(propertyName, BindingFlags.Static);
    return (Func<TProperty>)propertyInfo.GetMethod.CreateDelegate(typeof(Func<TProperty>));
}

看起来很简单,这就是为什么这是第一个例子。CreateDelegate方法需要创建委托类型。对于属性检索,Func只有一个参数,它是属性的类型,因此创建的委托将没有任何参数。只是简单的无参数方法,返回静态属性的值。

接下来是处理非公共(internal, protected private)属性。我们可以通过在GetProperty方法中更改设置 BindingFlags来实现。对于私人和受保护的成员,我们只需要一个BindingFlags.NonPublic。对于内部,这有点复杂(在我看来,更令人困惑)并且需要标志PublicNonPublic。理解这一点的一个简单方法是将内部结构视为一种公共的,但不是完全公共的(程序集内部可访问的)。

public static Func<TProperty> StaticPropertyGet<TProperty>(this Type source, string propertyName)
{
    var propertyInfo = source.GetProperty(propertyName, BindingFlags.Static);
    if (propertyInfo == null)
    {
        propertyInfo = source.GetProperty(propertyName, BindingFlags.Static | BindingFlags.NonPublic);
    }
    if (propertyInfo == null)
    {
        propertyInfo = source.GetProperty(propertyName,
            BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
    }
    return (Func<TProperty>)propertyInfo.GetMethod.CreateDelegate(typeof(Func<TProperty>));
}

上面的代码很容易理解。如果属性不是公开的,则会尝试将其设置为私有或受保护。如果情况不是这样,它会尝试内部的。现在,我们可以使用类型参数编写用于重载的方法体。

public static Func<TProperty> StaticPropertyGet<TSource, TProperty>(string propertyName)
{
    return typeof(TSource).StaticPropertyGet<TProperty>(propertyName);
}

现在我们可以测试两种方法。我们应该在控制台应用程序中为TestClass所有静态属性创建委托,并使用它们来检索属性值,以测试一切是否正常。

class ConsoleApplication
{
    private static readonly Type Type = typeof(TestClass);

    static void Main(string[] args)
    {
        var sp = DelegateFactory.StaticPropertyGet<TestClass, string>("StaticPublicProperty");
        var sp1 = Type.StaticPropertyGet<string>("StaticPublicProperty");
        var sp2 = Type.StaticPropertyGet<string>("StaticInternalProperty");
        var sp3 = Type.StaticPropertyGet<string>("StaticProtectedProperty");
        var sp4 = Type.StaticPropertyGet<string>("StaticPrivateProperty");

        Console.WriteLine("Static public property value: {0}",sp1());
        Console.WriteLine("Static internal property value: {0}", sp2());
        Console.WriteLine("Static protected property value: {0}", sp3());
        Console.WriteLine("Static private property value: {0}", sp4());
    }
}

运行此示例后,我们将在控制台中看到结果。

Static public property value: StaticPublicProperty
Static internal property value: StaticInternalProperty
Static protected property value: StaticProtectedProperty
Static private property value: StaticPrivateProperty

静态属性工作得很好。

我们可以稍微检查一下这些委托的性能。首先,我们需要为控制台应用程序类型添加两个新字段。

private static Stopwatch _stopWatch;
private static double _delay = 1e8;

其次是为测试编写循环。

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

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

第一个for循环以普通方式检索静态属性。第二个使用委托。运行更改后的代码后,我们可以发现差异并不大,真的。

Static Public property: 404
Static Public property retriever: 472

检索属性值一亿次需要大约400ms,使用委托的速度要慢不到20%。进行更多测试,您可以检查实际性能开销在15-35%之间变化。当然,它只是一个简单的字符串。对于首先计算其返回值的更复杂的属性,差异将更小。仅仅为了实验,我们可以添加一个带反射的循环。

_stopWatch = new Stopwatch();
var pi0 = Type.GetProperty("StaticPublicProperty", BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public).GetMethod;
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = pi0.Invoke(null, null);
}
_stopWatch.Stop();
Console.WriteLine("Static Public property via reflection: {0}", _stopWatch.ElapsedMilliseconds);

运行更改后的代码后,测试结果应与此类似:

Static Public property: 423
Static Public property retriever: 535
Static Public property via reflection: 13261

如您所见,反射速度要慢很多倍。访问静态属性的时间不到半秒,通过反射执行相同操作大约需要13秒!

即使使用反射,只有一次创建委托比使用它一直访问该属性更有效。

要全面了解事物,让我们再做一次测试。如果您曾经使用过Expression类型及其方法,那么您就知道它可以用于反射等类似的东西。当然,可以通过这种方式为静态属性创建委托。

public static Func<TProperty> StaticPropertyGet2<TProperty>(this Type source, string propertyName)
{
    var te = Expression.Lambda(Expression.Property(null, source, propertyName));
    return (Func<TProperty>)te.Compile();
}

这段代码比带反射的代码简单得多。它为TestClass.StaticPublicProperty创建一个lambda函数,看起来像这样。 

() => TestClass.StaticPublicProperty

现在,我们可以使用反射和表达式来测试委托创建的性能。

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < 10000; i++)
{
    Type.StaticPropertyGet<string>("StaticPublicProperty");
}
_stopWatch.Stop();
Console.WriteLine("Static public field creator via reflection: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < 10000; i++)
{
    Type.StaticPropertyGet2<string>("StaticPublicProperty");
}
_stopWatch.Stop();
Console.WriteLine("Static public field creator via expression: {0}", _stopWatch.ElapsedMilliseconds);

运行此示例后,我们将获得两个循环的结果。

Static public field creator via reflection: 23
Static public field creator via expression: 543

我们可以看到,表达式比反射慢得多。我没有进一步调查,但我认为它与编译表达式委托有关。不幸的是,不可能仅仅通过使用反射为所有成员创建委托,因此有必要为其中一些使用表达式,但我们将尽力不这样做。

当我们不知道属性的类型时,仍然存在这种情况。该怎么办?由于StaticPropertyGet都需要实际的属性类型,因此我们无法使用它们。在这种情况下,我们需要一个没有这种类型的重载,但是要使用什么替代呢?委托应该返回什么类型?只有一个选项:object.NET中的所有类型都可以转换为对象。

Func<object> spg = Type.StaticPropertyGet("StaticPublicProperty");

返回的委托应该是一个不接受参数并返回object类型值的方法。如何实现这个新的StaticPropertyGet?我们需要编码来检索PropertyInfo以获取所需的静态属性。最好的方法是使用StaticPropertyGet<TProperty>扩展方法中的代码在DelegateFactory创建一个新方法。

private static PropertyInfo GetStaticPropertyInfo(Type source, string propertyName)
{
    var propertyInfo = (source.GetProperty(propertyName, BindingFlags.Static) ??
                               source.GetProperty(propertyName, BindingFlags.Static | BindingFlags.NonPublic)) ??
                               source.GetProperty(propertyName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
    return propertyInfo;
}

主要问题是有时我们想要在编译时检索或设置未知类型的值的属性。为此,我们需要一个方法来创建委托,该委托检索对象类型值而不是属性类型值。我们可以通过使用表达式来实现,因为我们不能只使用CreateDelegate方法(它需要兼容的委托签名)。此机制的核心是Expression.Lambda方法,它返回LambdaExpression类型对象,以及返回Delegate类型的LambdaExpression.Compile方法。有了这两个,我们几乎可以创建任何涉及类型成员访问的代码。

public static Func<object> StaticPropertyGet(this Type source, string propertyName)
{
    var propertyInfo = GetStaticPropertyInfo(source, propertyName);
    return (Func<object>)Expression.Lambda(Expression.Call(propertyInfo.GetMethod)).Compile();
}

Expression.Lambda使用正确的方法签名创建lambda Expression.Call用于标记lambda的主体,应该调用属性getter——毕竟,属性getter实际上是get_ {property name}方法。

我们可以使用以下代码测试它是否正常工作。

var spg = Type.StaticPropertyGet("StaticPublicProperty");
Console.WriteLine(spg());

执行上面的行将写入'StaticPublicProperty'的值,这是具有此名称的property的值。 

设置访问(set accessors)

现在,当我们有办法获取静态属性的值时,我们可以编写用于检索设置(set)访问器的委托的方法。该机制是非常类似于获取访问器的,但不是使用Func<TProperty>,而是使用Action<TProperty>。当然,PropertyInfo.SetMethod创建委托而不是GetMethod

public static Action<TProperty> StaticPropertySet<TSource, TProperty>(string propertyName)
{
    return typeof(TSource).StaticPropertySet<TProperty>(propertyName);
}
public static Action StaticPropertySet(this Type source, string propertyName)
{
    var propertyInfo = GetStaticPropertyInfo(source, propertyName);
    return (Action)propertyInfo.SetMethod.CreateDelegate(typeof(Action));
}

和以前一样,对于获取(get)访问器,有两种方法只使用反射:一种是扩展方法,另一种是类型参数作为源类型。

对于不需要事先知道属性类型的重载,我们必须像以前一样使用StaticPropertyGet表达式。

public static Action<object> StaticPropertySet(this Type source, string propertyName)
{
    var propertyInfo = GetStaticPropertyInfo(source, propertyName);
    var valueParam = Expression.Parameter(typeof(object));
    var convertedValue = Expression.Convert(valueParam, propertyInfo.PropertyType);
    return (Action<object>)Expression.Lambda(Expression.Call(propertyInfo.SetMethod, convertedValue), valueParam).Compile();
}

这个表达式有点复杂。因为它应该返回返回void并且有一个参数的方法,我们需要将此参数添加到Lambda调用——因此是valueParam变量。如果您想知道为什么它不能嵌入到Expression.Lambda,则必须将相同的参数表达式传递给ConvertLambda方法——通过引用比较表达式。在传递给属性setter方法之前,必须将变量valueParam转换为正确的类型。如果没有,表达式将是有效的并且将无法创建。转换后,新表达式可以作为参数安全地传递给Call方法。valueParam变量中未转换的表达式作为参数传递给Lambda调用,要标记,创建的lambda将具有对象类型的单个参数。

完全以相同的方式调用新方法。

var sps = DelegateFactory.StaticPropertySet<TestClass, string>("StaticPublicProperty");
var sps1 = Type.StaticPropertySet<string>("StaticPublicProperty");
var sps2 = Type.StaticPropertySet<string>("StaticInternalProperty");
var sps3 = Type.StaticPropertySet<string>("StaticProtectedProperty");
var sps4 = Type.StaticPropertySet<string>("StaticPrivateProperty");<br />var spg5 = Type.StaticPropertyGet("StaticPublicProperty");

在控制台应用程序中,我们可以创建如下的测试循环。

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    TestInstance.PublicProperty = "ordinal way";
}
_stopWatch.Stop();
Console.WriteLine("Public set property: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    sps1("test");
}
_stopWatch.Stop();
Console.WriteLine("Public set property retriever: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    sps2("test");
}
_stopWatch.Stop();
Console.WriteLine("Internal set property retriever: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    sps3("test");
}
_stopWatch.Stop();
Console.WriteLine("Protected set property retriever: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    sps4("test");
}
_stopWatch.Stop();
Console.WriteLine("Private set property retriever: {0}", _stopWatch.ElapsedMilliseconds);

Console.WriteLine("Static public property value is {0}", sp1());
Console.WriteLine("Static internal property value is {0}", sp2());
Console.WriteLine("Static protected property value is {0}", sp3());
Console.WriteLine("Static private property value is {0}", sp4());

运行上面的代码后,我们将看到结果,其值与下面的值类似。

Public set property: 483
Public set property retriever: 542
Internal set property retriever: 496
Protected set property retriever: 497
Private set property retriever: 542
Static public property value is test
Static internal property value is test
Static protected property value is test
Static private property value is test

正如您所看到的,以这种方式访问​​setter只是稍慢一些,类似于通过委托访问getter 

改进

什么可以做得更好?当然,上面的代码不是很安全,因为当它可以抛出空引用异常时至少有两个点。首先,当找不到属性并且propertyInfo变量为null时,第二个是当获取(get)或设置(set) 访问器不可用时(并非每个属性都有,对吧?)。这两种情况都很容易解决。

我们应该首先编写属性来测试第一种情况。

public static string StaticOnlyGetProperty => _staticOnlyGetOrSetPropertyValue;

public static string StaticOnlySetProperty
{
    set { _staticOnlyGetOrSetPropertyValue = value; }
}
private static string _staticOnlyGetOrSetPropertyValue = "StaticOnlyGetOrSetPropertyValue";

现在我们可以修复这两种方法。

public static Func<TProperty> StaticPropertyGet<TProperty>(this Type source, string propertyName)
{
    var propertyInfo = GetStaticPropertyInfo(source, propertyName);
    return (Func<TProperty>)propertyInfo?.GetMethod?.CreateDelegate(typeof(Func<TProperty>));
}

public static Action<TProperty> StaticPropertySet<TProperty>(this Type source, string propertyName)
{
    var propertyInfo = GetStaticPropertyInfo(source, propertyName);
    return (Action<TProperty>)propertyInfo?.SetMethod?.CreateDelegate(typeof(Action<TProperty>));
}

如您所见,两种方法中的简单空检查就足够了,但无论如何我们都应该对其进行测试。在控制台应用程序中跟随调用就足够了。 

所有调用都返回null,这很好。我们想要null而不是错误。 

属性

通过覆盖静态属性,我们现在可以将焦点切换到实例属性。委托只有一个参数不同。由于实例属性需要实例来获取这些属性的值,因此委托将需要具有这些实例的参数。因此,我们将使用Func<TSource,TProperty>委托代替Func<TProperty>。其余代码几乎相同。至于静态属性,每个访问器有三种方法。首先,静态方法带有源类型参数和属性类型参数,扩展方法带有类型参数与属性类型相同,方法没有类型参数。但首先我们需要一个类似于GetStaticPropertyInfo的方法。

private static PropertyInfo GetPropertyInfo(Type source, string propertyName)
{
    var propertyInfo = source.GetProperty(propertyName) ??
                               source.GetProperty(propertyName, BindingFlags.NonPublic) ??
                               source.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
    return propertyInfo;
}

如您所见,它也非常相似。

我们将从每个访问器的第一种方法开始。

public static Func<TSource, TProperty> PropertyGet<TSource, TProperty>(string propertyName)
{
    var source = typeof(TSource);
    var propertyInfo = GetPropertyInfo(source, propertyName);
    return (Func<TSource, TProperty>)propertyInfo?.GetMethod?.CreateDelegate(typeof(Func<TSource, TProperty>));
}

public static Action<TSource, TProperty> PropertySet<TSource, TProperty>(string propertyName)
{
    var source = typeof(TSource);
    var propertyInfo = GetPropertyInfo(source, propertyName);
    return (Action<TSource, TProperty>)propertyInfo?.SetMethod?.CreateDelegate(typeof(Action<TSource, TProperty>));
}

如您所见,有一个空传播运算符(?。)用于属性信息和所需的访问器方法的空检查。

好。要测试它,我们需要在控制台应用程序中的TestClass中创建实例属性。

public class TestClass
{
    public TestClass()
    {
        PublicProperty = "PublicProperty";
        InternalProperty = "InternalProperty";
        ProtectedProperty = "ProtectedProperty";
        PrivateProperty = "PrivateProperty";
    }

    public string PublicProperty { get; set; }

    internal string InternalProperty { get; set; }

    protected string ProtectedProperty { get; set; }

    private string PrivateProperty { get; set; }

}

和控制台应用程序中的TestClass类型静态只读实例,以用于涉及实例的所有测试。

private static readonly TestClass TestInstance = new TestClass();

测试代码如下所示。

var ps1 = DelegateFactory.PropertySet<TestClass, string>("PublicProperty");
var ps2 = DelegateFactory.PropertySet<TestClass, string>("InternalProperty");
var ps3 = DelegateFactory.PropertySet<TestClass, string>("ProtectedProperty");
var ps4 = DelegateFactory.PropertySet<TestClass, string>("PrivateProperty");
            
Console.WriteLine("Public property value is {0}", pg1(TestInstance));
Console.WriteLine("Internal property value is {0}", pg2(TestInstance));
Console.WriteLine("Protected property value is {0}", pg3(TestInstance));
Console.WriteLine("Private property value is {0}", pg4(TestInstance));

ps1(TestInstance, "test");
ps2(TestInstance, "test");
ps3(TestInstance, "test");
ps4(TestInstance, "test");

Console.WriteLine("Public property value is {0}", pg1(TestInstance));
Console.WriteLine("Internal property value is {0}", pg2(TestInstance));
Console.WriteLine("Protected property value is {0}", pg3(TestInstance));
Console.WriteLine("Private property value is {0}", pg4(TestInstance));

运行此测试代码后,我们将看到这样的控制台输出。

Public property value is PublicProperty
Internal property value is InternalProperty
Protected property value is ProtectedProperty
Private property value is PrivateProperty
Public property value is test
Internal property value is test
Protected property value is test
Private property value is test

前四行是get委托返回的值,与TestClass实例的默认值一致。以下四行是在调用设置委托并具有新的测试值之后。好。一切正常!

改进

上述用于检索和设置实例属性的方法的主要问题是那些需要在编译时知道类型,这有时是不可能的,因为某些类型是私有的。但是,如果您没有类型,则无法创建具有此类型实例作为参数的委托。对此唯一的解决方案是创建一个接受对象的方法,并希望它是正确类型的正确实例。该解决方案的问题是您不能将对象类型的参数作为源实例传递给属性getter。首先必须将其强制转换为适当的类型。因此,在此委托方法中,您还可以转换为适当的类型,而不仅仅是一个委托方法,它可以检索或设置属性。这是我们必须在静态属性中使用表达式的地方。

第二个问题与静态属性相同。您不必在编译时知道属性类型。它可能是不可用的也可能是私有的。

首先,我们的新重载将如下所示:

Type.PropertyGet<string>("PublicProperty");
Type.PropertyGet("PublicProperty");

很简单。在第一种情况下,我们知道属性的类型,在第二种情况下——我们不知道。首先返回字符串,第二个返回相同的字符串,但作为对象类型。

然后我们需要为PropertyGet方法添加两个新的重载方法。让我们从具有已知属性类型的那个开始,因为它只是一点点,但是稍微简单一些。

使用GetPropertyInfo方法返回的属性信息,我们可以使用Expression类创建适当的委托。用于通过其实例作为对象访问属性getter的代码如下所示。

var sourceObjectParam = Expression.Parameter(typeof(object));<br />var @delegate = (Func<object, object>)Expression.Lambda(Expression.Call(Expression.Convert(sourceObjectParam, source), propertyInfo.GetMethod), sourceObjectParam).Compile();

机制很简单。首先,我们需要使用对象类型创建ParameterExpression。这是必要的,因为它被引用两次:在Expression.Lambda方法中作为目标lambda参数,在Expression.Convert方法中作为转换的目标。之后,我们使用带有方法调用(Expression.Call方法)的主体创建lambda表达式(Expression.Lambda方法),使用lambda对象参数的转换结果(Expression.Convert方法)调用参数调用属性getter 。它可能看起来很复杂(甚至更多,因为Expression API在我看来有点混乱;注意Lambda方法需要方法的第一个主体,然后参数,但Call方法首先接受一个调用的主体然后调用方法),但实际上它不是。编译后,表达式将大致相当于lambda,如下所示。

Func<object,string> @delegate = o => ((TestClass)o).PublicProperty;

看起来更清楚吧? 

有了这些知识,我们就可以创建第一种方法。

public static Func<object, TProperty> PropertyGet<TProperty>(this Type source, string propertyName)
{
    var propertyInfo = GetPropertyInfo(source, propertyName);
    var sourceObjectParam = Expression.Parameter(typeof(object));
    return (Func<object, TProperty>)Expression.Lambda(Expression.Call(Expression.Convert(sourceObjectParam, source), propertyInfo.GetMethod), sourceObjectParam).Compile();
}

以同样的方式我们可以创建第二个方法,但具有不同的返回类型声明。标记这一点非常重要,即使用C编写的带有返回类型对象的方法不需要在返回语句中强制转换为对象(因为任何类型都可以隐式转换为对象,而不需要额外的强制转换),这在表达式中也不起作用。据我所知,我的测试仅适用于参考类型。对于intDateTime等值类型,我们需要转换为objectPropertyInfo类具有IsClass属性,其允许检查是否需要转换。

public static Func<object, object> PropertyGet(this Type source, string propertyName)
{
    var propertyInfo = GetPropertyInfo(source, propertyName);
    var sourceObjectParam = Expression.Parameter(typeof(object));
    Expression returnExpression = 
        Expression.Call(Expression.Convert(sourceObjectParam, source), propertyInfo.GetMethod);
    if (!propertyInfo.PropertyType.IsClass)
    {
        returnExpression = Expression.Convert(returnExpression, typeof(object));
    }
    return (Func<object, object>)Expression.Lambda(returnExpression, sourceObjectParam).Compile();
}

如您所见,第二种方法更通用,几乎与第一种方法相同。因此,我们可以安全地重写第一种方法,如下所示。

public static Func<object, TProperty> PropertyGet<TProperty>(this Type source, string propertyName)
{
    return source.PropertyGet(propertyName) as Func<object, TProperty>;
}

在向TestClass添加新属性后使用int类型,PublicPropertyInt以及控制台应用程序的以下行,我们可以测试这些方法是否有效。

var pgo1 = Type.PropertyGet<string>("PublicProperty");
var pgo2 = Type.PropertyGet("PublicProperty");
var pgo3 = Type.PropertyGet("PublicPropertyInt");
Console.WriteLine("Public property by object and property type {0}", pgo1(TestInstance));
Console.WriteLine("Public property by objects {0}", pgo2(TestInstance));
Console.WriteLine("Public property by objects and with return value type {0}", pgo3(TestInstance));

它将导致控制台输出中的以下行。

Public property by object and property type PublicProperty
Public property by objects PublicProperty
Public property by objects and with return value type 0

现在,我们可以为setter编写类似的方法。核心区别在于正确的委托将是Action而不是Func,因此它将有两个:实例和值来设置,而不是一个参数。在具有已知参数类型的方法中,我们只有更改委托,但在没有类型参数的方法中,我们必须转换实例和值。我们将从第二个开始。首先,我们必须为新属性值创建ExpressionParameter

var propertyValueParam = Expression.Parameter(propertyInfo.PropertyType);

有了这个,我们可以改变lambda的返回值(如果我们改变类似的PropertyGet方法)。

return (Action<object, object>)Expression.Lambda(Expression.Call(<br />     Expression.Convert(sourceObjectParam, source), <br />     propertyInfo.SetMethod, <br />     Expression.Convert(propertyValueParam, propertyInfo.PropertyType)), sourceObjectParam, propertyValueParam).Compile();

如您所见,它创建了具有两个参数的Action委托——sourceObjectParampropertyValueParam——两者都首先转换为正确的类型。以上行可以转换为以下lambda

Action<object, object> @delegate = (i, v)=>((TestClass)i).PublicProperty = (string)v;

具有已知属性类型的PropertySet重载比上述方法更具体,但同时,它将具有两种类型的知识:值参数和属性类型。这就是我们可以将其代码用于两个重载的原因。只要属性类型正确,就不需要转换。

两者的最终版本看起来像这样。

public static Action<object, TProperty> PropertySet<TProperty>(this Type source, string propertyName)
{
    var propertyInfo = GetPropertyInfo(source, propertyName);
    var sourceObjectParam = Expression.Parameter(typeof(object));
    ParameterExpression propertyValueParam;
    Expression valueExpression;
    if (propertyInfo.PropertyType == typeof(TProperty))
    {
        propertyValueParam = Expression.Parameter(propertyInfo.PropertyType);
        valueExpression = propertyValueParam;
    }
    else
    {
        propertyValueParam = Expression.Parameter(typeof(TProperty));
        valueExpression = Expression.Convert(propertyValueParam, propertyInfo.PropertyType);
    }
    return (Action<object, TProperty>)Expression.Lambda(Expression.Call(Expression.Convert(sourceObjectParam, source), propertyInfo.SetMethod, valueExpression), sourceObjectParam, propertyValueParam).Compile();
}

public static Action<object, object> PropertySet(this Type source, string propertyName)
{
    return source.PropertySet<object>(propertyName);
}

应该修复的最后一件事是对GetMethodSetMethod进行空检查——毕竟它已经在前面的例子中完成了。这在PropertySetPropertyGet方法中需要单个if 

var propertyInfo = GetPropertyInfo(source, propertyName);
if (propertyInfo?.GetMethod == null)
{
    return null;
}

var propertyInfo = GetPropertyInfo(source, propertyName);
if (propertyInfo?.SetMethod == null)
{
    return null;
}

如果任何属性或访问器不存在(在所有属性不总是同时具有setget访问器之后)方法将返回null而不是委托。 

索引器

索引器只是特殊类型的实例属性。考虑它们的最佳方式是使用额外的索引参数作为特殊类型的getset访问器。它们实际上是名为“Item”的特殊属性。我们应该从在测试类中创建索引器开始。

private readonly List<int> _indexerBackend = new List<int>(10);

public int this[int i]
{
    get
    {
        return _indexerBackend[i];
    }
    set { _indexerBackend[i] = value; }
}

是的,没有索引检查,也没有自动收集大小调整等。它只是测试代码,所以它不需要是安全的。

这是非常简单的情况,因为这样的委托也将是非常复杂的。我们需要这个索引器有Func<TestInstance,int,int>。使用单个调用创建它的最简单方法如下所示。

var ig1 = DelegateFactory.IndexerGet<TestClass, int, int>();

如何实现IndexerGet方法?最重要的是为索引器获取PropertyInfo对象。

private const string Item = "Item";<br /><br />private static PropertyInfo GetIndexerPropertyInfo(Type source, Type returnType, Type[] indexesTypes)
{
    var propertyInfo = (source.GetProperty(Item, returnType, indexesTypes) ??
                        source.GetProperty(Item, BindingFlags.NonPublic, null, 
                            returnType, indexesTypes, null)) ?? 
                        source.GetProperty(Item, 
                            BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, 
                            null, returnType, indexesTypes, null);
    return propertyInfo;
}

对于公共索引器,获取PropertyInfo的方式与属性几乎相同——当然,需要两个新参数来指示所需索引器的返回和索引类型。对于非公共索引器,事情变得有点复杂,因为我们必须使用更一般的重载。当然,对GetProperty方法的所有调用都使用常量值“Item”作为属性的名称。使用属性信息,我们可以为索引器创建委托,其索引参数与普通属性的委托非常相似。

public static Func<TSource, TIndex, TReturn> IndexerGet<TSource, TReturn, TIndex>()
{
    var propertyInfo = GetIndexerPropertyInfo(typeof(TSource), typeof(TReturn), new[] { typeof(TIndex) });
    return (Func<TSource, TIndex, TReturn>)
            propertyInfo?.GetMethod?.CreateDelegate(typeof(Func<TSource, TIndex, TReturn>));
}

正如您所看到的,主要区别在于,不是只有一个带有实例的参数(比如在属性中),还有一个带有索引参数的额外参数。除此之外它几乎是一样的。

好的,但是如果索引器有多个索引呢?两三个怎么样?它也是可能的,因为IndexerGet方法采用类型参数,我们不能创建一个更通用的方法,返回2,3,4或更多参数的不同委托。对于这些类型的索引器中的每一种,需要具有不同类型参数集的其他方法。由于索引器很少使用,主要用于某种索引操作,因此我们将仅为最多3个索引参数(最多三维数组)创建方法。

public static Func<TSource, TIndex, TIndex2, TReturn> IndexerGet<TSource, TReturn, TIndex, TIndex2>()
{
    var propertyInfo = GetIndexerPropertyInfo(typeof(TSource), typeof(TReturn), new[] { typeof(TIndex), typeof(TIndex2) });
    return (Func<TSource, TIndex, TIndex2, TReturn>)
            propertyInfo?.GetMethod?.CreateDelegate(typeof(Func<TSource, TIndex, TIndex2, TReturn>));
}

public static Func<TSource, TIndex, TIndex2, TIndex2, TReturn> IndexerGet<TSource, TReturn, TIndex, TIndex2, TIndex3>()
{
    var propertyInfo = GetIndexerPropertyInfo(typeof(TSource), typeof(TReturn), new[] { typeof(TIndex), typeof(TIndex2), typeof(TIndex3) });
    return (Func<TSource, TIndex, TIndex2, TIndex2, TReturn>)
            propertyInfo?.GetMethod?.CreateDelegate(typeof(Func<TSource, TIndex, TIndex2, TIndex2, TReturn>));
}

上面的方法将为两个和三个索引生成委托,并且被称为非常类似于一维数组索引器重载。当然有可能编写方法来返回更多索引的委托,最多16个——这是Func类的限制。

var ig1 = DelegateFactory.IndexerGet<TestClass, int, int, int>();
var ig2 = DelegateFactory.IndexerGet<TestClass, int, int, int, int>();

您可能已经注意到,索引器getter的所有上述方法只有在我们知道类的类型时才能创建委托。如果我们不这样做(因为它是私有的或者在编译时不知道它),我们需要一个采用Type参数的方法。我们可以使用与之前属性相同的扩展方法约定。

var ig1 = Type.IndexerGet<int, int>();
var ig2 = Type.IndexerGet<int, int, int>();
var ig3 = Type.IndexerGet<int, int, int, int>();

索引器与之前的属性一样出现同样的问题。由于我们确实传递了实例而不是类型本身,因此我们无法创建具有已知类型参数的委托——它需要是对象。因此,我们再次需要表达式。我们将从具有单个索引参数的索引器开始。

public static Func<object, TIndex, TReturn> IndexerGet<TReturn, TIndex>(this Type source)
{
    var indexType = typeof(TIndex);
    var returnType = typeof(TReturn);
    var propertyInfo = GetIndexerPropertyInfo(source, returnType, new[] { indexType });
    var sourceObjectParam = Expression.Parameter(typeof(object));
    var paramExpression = Expression.Parameter(indexType);
    return (Func<object, TIndex, TReturn>)Expression.Lambda(
        Expression.Call(Expression.Convert(sourceObjectParam, source), propertyInfo.GetMethod, paramExpression), sourceObjectParam, paramExpression).Compile();
}

它与PropertyGet方法非常相似,它创建了将对象作为源实例的委托。差异在于索引参数。因为它是一个对象,所以首先需要转换。在创建了带有实例对象和索引参数的lambda表达式之后,它将被编译并作为委托返回。它可以表示为以下lambda语句。

Func<object, int, int> @delegate = (o, i) => ((TestClass)o)[i];

好。两三个索引怎么样?幸运的是,Expression.LambdaExpression.Call方法具有带有未指定数量的参数的重载。有了它,我们可以轻松地重写大多数用于创建委托的代码,更多的是通用版本。我们应该编写一个方法,通过从索引器返回类型和索引类型编译表达式来创建Delegate实例。

public static Delegate DelegateIndexerGet(Type source, Type returnType, params Type[] indexTypes)
{
    var propertyInfo = GetIndexerPropertyInfo(source, returnType, indexTypes);
    var sourceObjectParam = Expression.Parameter(typeof(object));
    var paramsExpression = new ParameterExpression[indexTypes.Length];
    for (var i = 0; i < indexTypes.Length; i++)
    {
        var indexType = indexTypes[i];
        paramsExpression[i] = Expression.Parameter(indexType);
    }
    return Expression.Lambda(
                Expression.Call(Expression.Convert(sourceObjectParam, source), propertyInfo.GetMethod, paramsExpression),
                new[] { sourceObjectParam }.Concat(paramsExpression)).Compile();
}

最重要的变化是此方法采用未指定数量的索引类型。第二个变化是,LambdaCall的参数集合不是已知的参数集合(在lambda中还有一个参数——源实例参数)。除此之外,它与以前的代码相同,使用这种新方法,我们可以实现上面的树扩展方法。

public static Func<object, TIndex, TReturn> IndexerGet<TReturn, TIndex>(this Type source)
{
    var indexType = typeof(TIndex);
    return (Func<object, TIndex, TReturn>)DelegateIndexerGet(source, typeof(TReturn), indexType);
}
        
public static Func<object, TIndex, TIndex2, TReturn> IndexerGet<TReturn, TIndex, TIndex2>(this Type source)
{
    var indexType = typeof(TIndex);
    var indexType2 = typeof(TIndex2);
    return (Func<object, TIndex, TIndex2, TReturn>)DelegateIndexerGet(source, typeof(TReturn), indexType, indexType2);
}

public static Func<object, TIndex, TIndex2, TIndex3, TReturn> IndexerGet<TReturn, TIndex, TIndex2, TIndex3>(this Type source)
{
    var indexType = typeof(TIndex);
    var indexType2 = typeof(TIndex2);
    var indexType3 = typeof(TIndex3);
    return (Func<object, TIndex, TIndex2, TIndex3, TReturn>)DelegateIndexerGet(source, typeof(TReturn), indexType, indexType2, indexType3);
}

这是简单的代码。类型参数更改为Type类的实例并传递给DelegateIndexerGet方法。返回值将转换为预期的Func类。但是这些新方法仍然缺乏一些功能。索引器不需要具有原始索引类型——它们可以是任何类型。即使很少出现这种情况,我们也必须有一种方法来创建更多索引参数的委托。但更重要的是索引器的返回类型——它可以是任何类型,它更常见。那我们需要一个更通用的方法。对于初学者,我们可以为只有一个参数的索引器创建委托Func<object,object,object> 的方法。

public static Func<object, object, object> IndexerGet(this Type source, Type returnType, Type indexType)
{
    var propertyInfo = GetIndexerPropertyInfo(source, returnType, new[] { indexType });
    var sourceObjectParam = Expression.Parameter(typeof(object));
    var indexObjectParam = Expression.Parameter(typeof(object));
    Expression returnExpression = Expression.Call(Expression.Convert(sourceObjectParam, source),
        propertyInfo.GetMethod, Expression.Convert(indexObjectParam, indexType));
    if (!propertyInfo.PropertyType.IsClass)
    {
        returnExpression = Expression.Convert(returnExpression, typeof(object));
    }
    return (Func<object, object, object>)
        Expression.Lambda(returnExpression, sourceObjectParam, indexObjectParam).Compile();
}

它与PropertyGet重载几乎相同,它可以处理对象而不是具体类型——它只需要一个参数。

理想情况下,必须有一种方法以统一的方式获取所有索引器类型。例如,可以编写一个带有两个参数的委托:实例和索引数组并返回对象。

var ig = Type.IndexerGet(typeof(int), typeof(int), typeof(int), typeof(int));
var t = ig(TestInstance, new object[] { 0, 0, 0 });

这种方法的问题在于它需要更多的计算,包括读取索引数组,将它们转换为正确的类型,调用索引器getter并将其值转换为object。因为所有这一切,它将比调用getter慢得多,但如果我们没有更严格的委托(如Func<TSource, TIndex, TReturn>),那么这是我们能做的最好的事情。

public static Func<object, object[], object> IndexerGet(this Type source, Type returnType, params Type[] indexTypes)
{
    var propertyInfo = GetIndexerPropertyInfo(source, returnType, indexTypes);
    var sourceObjectParam = Expression.Parameter(typeof(object));
    var indexesParam = Expression.Parameter(typeof(object[]));
    var paramsExpression = new Expression[indexTypes.Length];
    for (var i = 0; i < indexTypes.Length; i++)
    {
        var indexType = indexTypes[i];
        paramsExpression[i] = Expression.Convert(Expression.ArrayIndex(indexesParam, Expression.Constant(i)), indexType);
    }
    Expression returnExpression =
        Expression.Call(Expression.Convert(sourceObjectParam, source), propertyInfo.GetMethod, paramsExpression);
    if (!propertyInfo.PropertyType.IsClass)
    {
        returnExpression = Expression.Convert(returnExpression, typeof(object));
    }
    return (Func<object, object[], object>)Expression.Lambda(
        returnExpression, sourceObjectParam, indexesParam).Compile();
}

以前方法的主要变化是我们有一个参数,所有索引都在ParameterExpression传递给lambda 

var indexesParam = Expression.Parameter(typeof(object[]));

由于这是一个索引数组,我们需要从中获取每一个索引。它可以通过Expression.ArrayIndex方法完成。检索索引值后,我们需要将其转换为同一位置的索引类型。通过收集已转换的索引,我们可以将它作为索引器getter参数的集合传递给Expression.Call。有类似的变化(如果我们将代码与没有类型参数的PropertyGet方法进行比较),它返回对象而不是属性类型实例:如果返回类型是值类型(intbyte等),则完成对象的转换。这是对更常见的lambda语句的翻译。

Func<object, object[], object> @delegate = (o, i) => (object)((TestClass)o)[(int)i[0], (int)i[1], (int)i[3]];

是的,当然没有检查索引或实例的类型是否正确。没有检查索引数组是否具有正确的大小,也不检查索引类型。有很多事情可能会出错。但我们写委托是为了速度而不是安全。如果我们谈论性能,那么现在是测试它的时候了。我们应该首先向TestClass添加一些索引器。

public int this[int i1, int i2, int i3]
{
    get { return i1; }
    set
    {
    }
}

internal string this[string s] => s;

private long this[long s] => s;

private int this[int i1, int i2]
{
    get { return i1; }
    set { }
}

上述索引器和第一个索引器的测试代码如下所示。

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

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

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = TestInstance[0, 0, 0];
}
_stopWatch.Stop();
Console.WriteLine("Multiple index indexer directly: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = ig10(TestInstance, new object[] { 0, 0, 0 });
}
_stopWatch.Stop();
Console.WriteLine("Multiple index indexer via delegate with array: {0}", _stopWatch.ElapsedMilliseconds);

var indexerInfo = Type.GetProperty("Item", typeof(int), new Type[] { typeof(int), typeof(int), typeof(int) });
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = indexerInfo.GetValue(TestInstance, new object[] { 0, 0, 0 });
}
_stopWatch.Stop();
Console.WriteLine("Multiple index indexer via reflection: {0}", _stopWatch.ElapsedMilliseconds);

运行此代码将导致控制台输出如下所示。

Public indexer: 1555
Public indexer retriever: 1603<br />Multiple index indexer directly: 525
Multiple index indexer via delegate with array: 5847
Multiple index indexer via reflection: 44755

正如您所看到的,通过代理委托调用索引器并不是那么慢。当然,对于比在后端字段上调用索引操作更复杂的索引器,就像在TestClass中的第一个索引器一样这种差异将更加明显。另一件值得一提的是,使用数组而不是参数集调用delegate使得它比原始索引器慢得多。在测试索引器中,它慢了大约十倍。区别是如此之大,因为索引器本身只是一个返回操作,委托将必须索引和转换所有参数并将其传递给索引器。对于具有三个以上参数的索引器,差异可能更大(另一个原因是为什么编写这样的索引器而不是单个方法)。另一方面,如果索引器会更复杂,那么差异就会变得不那么明显。无论如何,委托仍然比反射调用快几倍。 

改进

在某些情况下,可能存在一个小问题。索引器名称不一定必须是Item。根据文档,它可以像这样改变。

 

[System.Runtime.CompilerServices.IndexerName("TheItem")]
internal string this[string s] => s;

有没有人用过它?我不认为这很常见,但没关系。无论如何,我们可以使它工作。我们可以使用索引器名称参数编写方法的新重载,但是它需要太多的工作,因为我们聪明而懒惰,有一种更好的方法。有可能重命名索引器属性,但您必须在同一个类中同时对所有索引器执行此操作。GetIndexerPropertyInfo方法已经几次查找索引器信息。为什么不强迫它多做几次呢?如果不存在Item,我们可以搜索作为索引器的其他属性。

private static PropertyInfo GetIndexerPropertyInfo(Type source, Type returnType, Type[] indexesTypes,
    string indexerName = null)
{
    indexerName = indexerName ?? Item;

    var propertyInfo = (source.GetProperty(indexerName, returnType, indexesTypes) ??
                        source.GetProperty(indexerName, BindingFlags.NonPublic, null,
                            returnType, indexesTypes, null)) ??
                        source.GetProperty(indexerName,
                            BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
                            null, returnType, indexesTypes, null);
    if (propertyInfo != null)
    {
        return propertyInfo;
    }
    var indexer = source.GetProperties().FirstOrDefault(p => p.GetIndexParameters().Length > 0);
    return indexer != null ? GetIndexerPropertyInfo(source, returnType, indexesTypes, indexer.Name) : null;
}

从反射返回的索引器属性没有从方法GetIndexParameters返回的null数组。这样,我们可以找到所有索引器的名称,然后针对我们真正寻找的那个进行更具体的搜索。DelegatesFactory最终用户根本不必考虑索引器的名称(这很好,因为索引器在C#代码中没有名称)。

要完成有关索引器getter的主题,我们需要检查propertyInfo变量和PropertyInfo.Get / SetMethod属性(如果它们不为null)。它应该与属性相同。

if (propertyInfo?.GetMethod == null)
{
    return null;
}

Setters

现在我们可以跳转到索引器的setter。这与属性的setter非常相似。首先,我们使用Action类而不是Func其次我们还有一个参数值可以在索引器中设置。就这些。例如,转换为IndexerSet后的第一个IndexerGet方法如下所示。

public static Action<TSource, TIndex, TProperty> IndexerSet<TSource, TIndex, TProperty>()
{
    var sourceType = typeof(TSource);
    var propertyInfo = GetIndexerPropertyInfo(sourceType, typeof(TProperty), new[] { typeof(TIndex) });
    return (Action<TSource, TIndex, TProperty>)
        propertyInfo?.SetMethod?.CreateDelegate(typeof(Action<TSource, TIndex, TProperty>));
}

2D3D索引器的方法也非常相似。

public static Action<TSource, TIndex, TIndex2, TReturn> IndexerSet<TSource, TReturn, TIndex, TIndex2>()
{
    var propertyInfo = GetIndexerPropertyInfo(typeof(TSource), typeof(TReturn), new[] { typeof(TIndex), typeof(TIndex2) });
    return (Action<TSource, TIndex, TIndex2, TReturn>)
            propertyInfo?.SetMethod?.CreateDelegate(typeof(Action<TSource, TIndex, TIndex2, TReturn>));
}

public static Action<TSource, TIndex, TIndex2, TIndex2, TReturn> IndexerSet<TSource, TReturn, TIndex, TIndex2, TIndex3>()
{
    var propertyInfo = GetIndexerPropertyInfo(typeof(TSource), typeof(TReturn), new[] { typeof(TIndex), typeof(TIndex2), typeof(TIndex3) });
    return (Action<TSource, TIndex, TIndex2, TIndex2, TReturn>)
            propertyInfo?.SetMethod?.CreateDelegate(
                typeof(Action<TSource, TIndex, TIndex2, TIndex2, TReturn>));
}

使用索引数组的方法,它有点复杂。

public static Action<object, object[], object> IndexerSet(this Type source, Type returnType, params Type[] indexTypes)
{
    var propertyInfo = GetIndexerPropertyInfo(source, returnType, indexTypes);
    if (propertyInfo?.SetMethod == null)
    {
        return null;
    }
    var sourceObjectParam = Expression.Parameter(typeof(object));
    var indexesParam = Expression.Parameter(typeof(object[]));
    var valueParam = Expression.Parameter(typeof(object));
    var paramsExpression = new Expression[indexTypes.Length + 1];
    for (var i = 0; i < indexTypes.Length; i++)
    {
        var indexType = indexTypes[i];
        paramsExpression[i] = Expression.Convert(Expression.ArrayIndex(indexesParam, Expression.Constant(i)), indexType);
    }
    paramsExpression[indexTypes.Length] = Expression.Convert(valueParam, returnType);
    Expression returnExpression =
        Expression.Call(Expression.Convert(sourceObjectParam, source), propertyInfo.SetMethod, paramsExpression);
    return (Action<object, object[], object>)Expression.Lambda(
        returnExpression, sourceObjectParam, indexesParam, valueParam).Compile();
}

第一个区别当然是Action而不是Func。其次是在Expression.Convert中使用returnType参数来改变valueParam参数的类型,这是Expression.Call的最后一个参数。

var valueParam = Expression.Parameter(typeof(object));<br />paramsExpression[indexTypes.Length] = Expression.Convert(valueParam, returnType)

相同的valueParam变量作为Expression.Lambda方法的最后一个参数(在索引数组之后)传递。其他所有内容都与PropertyGetPropertySetIndexerGet中的代码类似。

使用扩展方法,在新的DelegateIndexerSet方法中更改。

public static Delegate DelegateIndexerSet(Type source, Type returnType,
    params Type[] indexTypes)
{
    var propertyInfo = GetIndexerPropertyInfo(source, returnType, indexTypes);
    if (propertyInfo?.SetMethod == null)
    {
        return null;
    }
    var sourceObjectParam = Expression.Parameter(typeof(object));
    var valueParam = Expression.Parameter(returnType);
    var indexExpressions = new ParameterExpression[indexTypes.Length];
    for (var i = 0; i < indexTypes.Length; i++)
    {
        var indexType = indexTypes[i];
        indexExpressions[i] = Expression.Parameter(indexType);
    }
    var callArgs = indexExpressions.Concat(new [] { valueParam }).ToArray();
    var paramsExpressions = new[] { sourceObjectParam }.Concat(callArgs);
    return Expression.Lambda(
                Expression.Call(Expression.Convert(sourceObjectParam, source),
                    propertyInfo.SetMethod, callArgs), paramsExpressions).Compile();
}

当然,使用SetMethod而不是getter等效项。此外,还有如上述方法中的新变量valueParam。新的集合,callArgsparamesExpressions允许我们将适当的值传递给lambasetter调用。第一个是索引参数表达式和值表达式的集合。第二个是源实例参数表达式,相同的索引表达式和最后的值参数。

最后,我们可以使用新的DelegateIndexerSet方法编写最后一个IndexerSet方法。

public static Action<object, TIndex, TReturn> IndexerSet<TReturn, TIndex>(this Type source)
{
    var indexType = typeof(TIndex);
    return (Action<object, TIndex, TReturn>)DelegateIndexerSet(source, typeof(TReturn), indexType);
}

public static Action<object, TIndex, TIndex2, TReturn> IndexerSet<TReturn, TIndex, TIndex2>(this Type source)
{
    var indexType = typeof(TIndex);
    var indexType2 = typeof(TIndex2);
    return (Action<object, TIndex, TIndex2, TReturn>)DelegateIndexerSet(source, typeof(TReturn), indexType, indexType2);
}

public static Action<object, TIndex, TIndex2, TIndex3, TReturn> IndexerSet<TReturn, TIndex, TIndex2, TIndex3>(this Type source)
{
    var indexType = typeof(TIndex);
    var indexType2 = typeof(TIndex2);
    var indexType3 = typeof(TIndex3);
    return (Action<object, TIndex, TIndex2, TIndex3, TReturn>)DelegateIndexerSet(source, typeof(TReturn), indexType, indexType2, indexType3);
}

这些方法与get访问器的方法完全相同。他们只是创建不同的委托。

var is1 = DelegateFactory.IndexerSet<TestClass, int, int>();
var is2 = DelegateFactory.IndexerSet<TestClass, int, int, int>();
var is3 = DelegateFactory.IndexerSet<TestClass, int, int, int, int>();
var is4 = Type.IndexerSet(typeof(int), typeof(int), typeof(int), typeof(int));
var is5 = Type.IndexerSet<int, int>();
var is6 = Type.IndexerSet<int, int, int>();
var is7 = Type.IndexerSet<int, int, int, int>();

is1(TestInstance, 0, 1);
is2(TestInstance, 0, 0, 1);
is3(TestInstance, 0, 0, 0, 1);
is4(TestInstance, new object[] { 0, 0, 0 }, 1);
is5(TestInstance, 1, 2);
is6(TestInstance, 0, 0, 2);
is7(TestInstance, 0, 0, 0, 2);

静态字段

我们有属性和索引器。现在我们应该尝试为字段制作委托。首先是静态的。怎么做?对于属性(或相关方法),我们为每个访问器都有MethodInfo对象,但是字段是字段,它们没有访问器,实际上只有具有特殊名称的方法。因此,我们无法从反射返回的FieldInfo创建委托。我们只能通过从表达式创建委托来实现。Expression.Field正是对那种数据的访问。

但在实现任何新方法之前,现在是编写一些测试静态字段的时候了。

public static string StaticPublicField = "StaticPublicField";

public static string StaticInternalField = "StaticInternalField";

public static string StaticProtectedField = "StaticProtectedField";

public static string StaticPrivateField = "StaticPrivateField";

获取静态字段值

创建将返回静态字段值的委托的新方法称为StaticFieldGet。首先,我们将编写将类型作为类型参数的重载——在编译时用于已知类型。它们将以下列方式调用。

var sfg1 = DelegateFactory.StaticFieldGet<TestClass, string>("StaticPublicField");

最简单的实现如下所示。

public static Func<TField> StaticFieldGet<TSource, TField>(string fieldName)
{
    var source = typeof(TSource);
    var lambda = Expression.Lambda(Expression.Field(null, source, fieldName));
    return (Func<TField>)lambda.Compile();
}

与委托的一些索引器表达式相比,简单得多。在lambda语句中,相同的表达式更加清晰。

Func<string> @delegate = () => TestClass.StaticPublicField;

返回值的简单函数。好的,但是如果字段名称无效会发生什么?表达式将失败,应用程序将崩溃。使用这样的代码,除了try-catch语句之外,我们无法以任何方式解决这个问题。幸运的是,Expression.Field方法还有另一个重载,它接受FieldInfo对象而不是带字段名的字符串。我们可以自己搜索所需的字段。如果未找到字段且FieldInfo对象为null,则将返回null值而不是StaticFieldGet方法中的委托。要检索字段数据,我们可以基于GetPropertyInfo方法的代码创建一个新方法GetFieldInfo

private static FieldInfo GetStaticFieldInfo(Type source, string fieldName)
{
    var fieldInfo = (source.GetField(fieldName, BindingFlags.Static) ??
                          source.GetField(fieldName, BindingFlags.Static | BindingFlags.NonPublic)) ??<br />                          source.GetField(fieldName, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
    return fieldInfo;
}

现在,我们可以使用字段信息编写更安全的方法。

public static Func<TField> StaticFieldGet<TSource, TField>(string fieldName)
{
    var fieldInfo = GetStaticFieldInfo(typeof(TSource), fieldName);
    if (fieldInfo != null)
    {
        var lambda = Expression.Lambda(Expression.Field(null, fieldInfo));
        return (Func<TField>)lambda.Compile();
    }
    return null;
}

有一个使用字段数据作为参数的Expression.Field方法的空检查和另一个重载。其余的代码已经用不同的方法介绍了。

如果将纯表达式版本重命名为StaticFieldGetExpr我们还可以测试两种方法的性能差异

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < 10000; i++)
{
    DelegateFactory.StaticFieldGet<TestClass, string>("StaticPublicField");
}
_stopWatch.Stop();
Console.WriteLine("Static public field creator via reflection: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < 10000; i++)
{
    DelegateFactory.StaticFieldGetExpr<TestClass, string>("StaticPublicField");
}
_stopWatch.Stop();
Console.WriteLine("Static public field creator via expression: {0}", _stopWatch.ElapsedMilliseconds);

根据性能测试,差异可以忽略不计,或者反射的小指示也可以更快。

Static public field creator via reflection: 538
Static public field creator via expression: 640

但是,如果我们既没有源类型也没有字段类型,甚至两者都没有呢?毕竟,字段几乎总是私有的,可以有私有类型。

和以前一样,我们可以通过添加新的重载来解决这个问题。

var sfg1 = Type.StaticFieldGet<string>("StaticPublicField");
var sfg2 = Type.StaticFieldGet("StaticPublicField");

与属性和索引器相同,第一个重载应该返回字段类型,第二个只是对象。

public static Func<TField> StaticFieldGet<TField>(this Type source,
    string fieldName)
{
    var fieldInfo = GetStaticFieldInfo(source, fieldName);
    if (fieldInfo != null)
    {
        var lambda = Expression.Lambda(Expression.Field(null, fieldInfo));
        return (Func<TField>)lambda.Compile();
    }
    return null;
}

public static Func<object> StaticFieldGet(this Type source, string fieldName)
{
    var fieldInfo = GetStaticFieldInfo(source, fieldName);
    if (fieldInfo != null)
    {
        Expression returnExpression = Expression.Field(null, fieldInfo);
        if (!fieldInfo.FieldType.IsClass)
        {
            returnExpression = Expression.Convert(returnExpression, typeof(object));
        }
        var lambda = Expression.Lambda(returnExpression);
        return (Func<object>)lambda.Compile();
    }
    return null;
}

如果字段具有值类型,则执行转换为对象。除此之外,它几乎与之前的重载相同。因此,可以像这样简化先前的方法。

public static Func StaticFieldGet<TSource, TField>(string fieldName)
{
    var source = typeof(TSource);
    return source.StaticFieldGet(fieldName);
}

现在,以同样简单的方式,我们可以检索具有所有可见性的字段。

var sfg3 = Type.StaticFieldGet<string>("StaticPublicField");
var sfg4 = Type.StaticFieldGet<string>("StaticInternalField");
var sfg5 = Type.StaticFieldGet<string>("StaticProtectedField");
var sfg6 = Type.StaticFieldGet<string>("StaticPrivateField");

Console.WriteLine("Static public field value: {0}", sfg3());
Console.WriteLine("Static internal field value: {0}", sfg4());
Console.WriteLine("Static protected field value: {0}", sfg5());
Console.WriteLine("Static private field value: {0}", sfg6());

在测试控制台应用程序中运行它之后,我们将设置所有字段的正确值。

Static public field value: StaticPublicField
Static internal field value: StaticInternalField
Static protected field value: StaticProtectedField
Static private field value: StaticPrivateField

设置静态字段值

我们现在有办法检索静态字段的值。改变它们的可能性怎么样?它可以和属性和索引器一样完成。大多数代码保持不变。当然,返回的委托将是Action  类的。仍然存在Expression.Field,但需要与Expression.Assign结合才能使用赋值操作更改字段值。

对于检索字段值,我们有三个方法:static,带有字段类型的扩展方法和返回对象的扩展方法。对于更改字段,我们还将编写三个重载。

var sfs1 = DelegateFactory.StaticFieldSet<TestClass, string>("StaticPublicField");
var sfs2 = Type.StaticFieldSet<string>("StaticPublicField");
var sfs3 = Type.StaticFieldSet("StaticPublicField");

由于第一个可以很容易地重写为第二个的代理,让我们用第二个代理开始实现。

public static Action<TField> StaticFieldSet<TField>(this Type source,
    string fieldName)
{
    var fieldInfo = GetStaticFieldInfo(source, fieldName);
    if (fieldInfo != null)
    {
        var valueParam = Expression.Parameter(typeof(TField));
        var lambda = Expression.Lambda(typeof(Action<TField>),
            Expression.Assign(Expression.Field(null, fieldInfo), valueParam), valueParam);
        return (Action<TField>)lambda.Compile();
    }
    return null;
}

正如您所看到的,大多数更改都在表达式本身中,因为没有对Expression.Field的单个调用,但它嵌套在Expression.Assign以将lambda body 赋值参数标记为field。当然,生成的委托是Action而不是Func。对于TestClass.StaticPublicField字段,它将生成如下的lambda

Action<string> @delegate = v => TestClass.StaticPublicField = v;

第一次重载将如下所示。

public static Action<TField> StaticFieldSet<TSource, TField>(string fieldName)
{
    var source = typeof(TSource);
    return source.StaticFieldSet<TField>(fieldName);
}

第三个更有趣,但在索引器之后仍然没有什么真正难以理解的。

public static Action<object> StaticFieldSet(this Type source,
    string fieldName)
{
    var fieldInfo = GetStaticFieldInfo(source, fieldName);
    if (fieldInfo != null)
    {
        var valueParam = Expression.Parameter(typeof(object));
        var convertedValueExpr = Expression.Convert(valueParam, fieldInfo.FieldType);
        var lambda = Expression.Lambda(typeof(Action<object>),
            Expression.Assign(Expression.Field(null, fieldInfo), convertedValueExpr), valueParam);
        return (Action<object>)lambda.Compile();
    }
    return null;
}

正如所料,值参数表达式在此类型为object的方法中,而不是与字段相同的类型中。因此,它需要在分配之前首先进行转换。与TestClass.StaticPublicField lambda相同的委托将类似于下面的lambda

Action<object> @delegate = v => TestClass.StaticPublicField = (string)v;

是时候测试这些方法是否有效。

var sfs1 = DelegateFactory.StaticFieldSet<TestClass, string>("StaticPublicField");
var sfs2 = Type.StaticFieldSet<string>("StaticPublicField");
var sfs3 = Type.StaticFieldSet("StaticPublicField");

sfs1("test1");
Console.WriteLine("Static public field value: {0}", TestClass.StaticPublicField);
sfs2("test2");
Console.WriteLine("Static public field value: {0}", TestClass.StaticPublicField);
sfs3("test3");
Console.WriteLine("Static public field value: {0}", TestClass.StaticPublicField);

上面的代码将导致控制台输出如下。

Static public field value: test1
Static public field value: test2
Static public field value: test3

字段

我们已经在静态字段上练习,因此实例字段应该不是问题。主要变化是所有委托中的额外参数。因此,我们不需要在Expression.Field调用中使第一个参数(源实例)为空。除此之外,一切都保持不变。

我们应该先将一些字段添加到TestClass类型中。

public string PublicField;

internal string InternalField;

protected string ProtectedField;

private string _privateField;

public TestClass()
{
    PublicField = "PublicField";
    InternalField = "InternalField";
    ProtectedField = "ProtectedField";
    _privateField = "_privateField";
}

获取字段值委托

 当然,我们需要相同的三种方法作为实例字段。

var fg1 = DelegateFactory.FieldGet<TestClass, string>("PublicField");
var fg2 = Type.FieldGet<string>("PublicField");
var fg3 = Type.FieldGet("PublicField");

首先,我们必须创建一个类似的方法来获取FieldInfo对象,就像在GetStaticFieldInfo中一样

private static FieldInfo GetFieldInfo(Type source, string fieldName)
{
    var fieldInfo = (source.GetField(fieldName) ??
                          source.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic)) ??
                          source.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
    return fieldInfo;
}

几乎相同,但它使用BindingFlags枚举的实例值而不是静态值。

让我们从最后一个FieldGet  方法开始——它应该是最复杂的。

public static Func<object, object> FieldGet(this Type source, string fieldName)
{
    var fieldInfo = GetFieldInfo(source, fieldName);
    if (fieldInfo != null)
    {
        var sourceParam = Expression.Parameter(typeof(object));
        Expression returnExpression = Expression.Field(Expression.Convert(sourceParam, source), fieldInfo);
        if (!fieldInfo.FieldType.IsClass)
        {
            returnExpression = Expression.Convert(returnExpression, typeof(object));
        }
        var lambda = Expression.Lambda(returnExpression, sourceParam);
        return (Func<object, object>)lambda.Compile();
    }
    return null;
}

它与PropertyGet方法和StaticFieldGet类似。返回的Func将实例作为对象检索字段,将其强制转换为正确的实例类型并检索字段值,如果它是值类型,则将其转换为对象,即对于TestClass.PublicField,它将像这样创建委托。

Func<object, object> @delegate = i => ((TestClass)i).PublicField;

现在,同样的方法可以通过安全的转换为其他两个方法创建委托。

public static Func<TSource, TField> FieldGet<TSource, TField>(string fieldName)
{
    var source = typeof(TSource);
    return source.FieldGet(fieldName) as Func<TSource, TField>;
}

public static Func<object, TField> FieldGet<TField>(this Type source,
    string fieldName)
{
    return source.FieldGet(fieldName) as Func<object, TField>;
}

我没有真正研究它为什么这样做(例如,使用ILDASM应用程序),但我怀疑过度的转换是通过JIT编译优化的,因为生成的委托之间没有真正的性能差异。

我们可以通过测试控制台应用程序中的行来测试新方法。

var fg1 = DelegateFactory.FieldGet<TestClass, string>("PublicField");
var fg2 = DelegateFactory.FieldGet<TestClass, string>("InternalField");
var fg3 = DelegateFactory.FieldGet<TestClass, string>("ProtectedField");
var fg4 = DelegateFactory.FieldGet<TestClass, string>("_privateField");

var fg5 = Type.FieldGet<string>("PublicField");
var fg6 = Type.FieldGet("PublicField");

Console.WriteLine("Public field value: {0}", fg1(TestInstance));
Console.WriteLine("Internal field value: {0}", fg2(TestInstance));
Console.WriteLine("Protected field value: {0}", fg3(TestInstance));
Console.WriteLine("Private field value: {0}", fg4(TestInstance));
Console.WriteLine("Public field value by object and field type: {0}", fg5(TestInstance));
Console.WriteLine("Public field value by objects: {0}", fg6(TestInstance));

它将在Windows控制台中生成以下输出。

Public field value: PublicField
Internal field value: InternalField
Protected field value: ProtectedField
Private field value: _privateField
Public field value by object and field type: PublicField
Public field value by objects: PublicField

我们还可以测试直接字段访问和代理委托之间的性能差异。

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

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

var fieldInfo = Type.GetField("PublicField");
_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    var test = fieldInfo.GetValue(TestInstance);
}
_stopWatch.Stop();
Console.WriteLine("Public field via reflection: {0}", _stopWatch.ElapsedMilliseconds);

此性能测试将产生类似的结果。

Public field directly: 257
Public field retriever: 545<br />Public field via reflection: 9721

正如您所看到的,差异很大——超过100%。但毕竟,访问公共字段没有任何意义,除非您在编译时不知道类型或字段根本不公开。在这些情况下,最好是访问速度较慢但仍然可以访问。 除非你使用即使更大的性能差异也很好,而这些差异是使用了大约慢20倍的反射造成的。

设置字段值委托

由于我们已经在属性和静态字段中训练了值的设置,因此在实例字段中设置值应该不是问题。

我们仍然需要类似的三种方法:静态,按对象扩展和带字段类型的扩展。

var fs1 = DelegateFactory.FieldSet2<TestClass, string>("_privateField");
var fs2 = Type.FieldSet<string>("_privateField");          
var fs3 = Type.FieldSet("PublicField");

好的,这一次我们将再次从应该更复杂的最后一个开始。前两个应该能够从第三个委派委托。

public static Action<object, object> FieldSet(this Type source, string fieldName)
{
    var fieldInfo = GetFieldInfo(source, fieldName);
    if (fieldInfo != null)
    {
        var sourceParam = Expression.Parameter(typeof(object));
        var valueParam = Expression.Parameter(typeof(object));
        var convertedValueExpr = Expression.Convert(valueParam, fieldInfo.FieldType);
        Expression returnExpression = Expression.Assign(Expression.Field(Expression.Convert(sourceParam, source), fieldInfo), convertedValueExpr);
        if (!fieldInfo.FieldType.IsClass)
        {
            returnExpression = Expression.Convert(returnExpression, typeof(object));
        }
        var lambda = Expression.Lambda(typeof(Action<object, object>),
            returnExpression, sourceParam, valueParam);
        return (Action<object, object>)lambda.Compile();
    }
    return null;
}

与实例属性一样,我们以Action形式委托具有两个参数:第一个是实例,然后是要设置的值。两者都是对象。与静态字段类似,我们通过Expression.AssignExpression.Field将值赋给字段。Field 必须将转换后的source参数转换为实例类型,而不是像静态字段中那样使用null。除了代码非常相似。可以通过FieldGet中的简单转换实现另外两种方法

public static Action<object, TProperty> FieldSet<TProperty>(this Type source, string fieldName)
{
    return source.FieldSet(fieldName) as Action<object, TProperty>;
}

public static Action<TSource, TProperty> FieldSet<TSource, TProperty>(string fieldName)
{
    return typeof(TSource).FieldSet(fieldName) as Action<TSource, TProperty>;
}

它可以工作,但根据测试,它比具体实现慢一点。直接创建委托的版本,不进行强制转换,如下所示。

public static Action<object, TProperty> FieldSet2<TProperty>(this Type source, string fieldName)
{
    var fieldInfo = GetFieldInfo(source, fieldName);
    if (fieldInfo != null)
    {
        var sourceParam = Expression.Parameter(typeof(object));
        var valueParam = Expression.Parameter(typeof(TProperty));
        var te = Expression.Lambda(typeof(Action<object, TProperty>),
            Expression.Assign(Expression.Field(Expression.Convert(sourceParam, source), fieldInfo), valueParam),
            sourceParam, valueParam);
        return (Action<object, TProperty>)te.Compile();
    }
    return null;
}

public static Action<TSource, TProperty> FieldSet2<TSource, TProperty>(string fieldName)
{
    var source = typeof(TSource);
    var fieldInfo = GetFieldInfo(source, fieldName);
    if (fieldInfo != null)
    {
        var sourceParam = Expression.Parameter(source);
        var valueParam = Expression.Parameter(typeof(TProperty));
        var te = Expression.Lambda(typeof(Action<TSource, TProperty>),
            Expression.Assign(Expression.Field(sourceParam, fieldInfo), valueParam),
            sourceParam, valueParam);
        return (Action<TSource, TProperty>)te.Compile();
    }
    return null;
}

导致这个结论的性能测试代码看起来像这样。

var fs1 = DelegateFactory.FieldSet<TestClass, string>("PublicField");
var fs2 = DelegateFactory.FieldSet2<TestClass, string>("PublicField");

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    fs1(TestInstance, "test");
}
_stopWatch.Stop();
Console.WriteLine("Public field set retriever with cast: {0}", _stopWatch.ElapsedMilliseconds);

_stopWatch = new Stopwatch();
_stopWatch.Start();
for (var i = 0; i < _delay; i++)
{
    fs2(TestInstance, "test");
}
_stopWatch.Stop();
Console.WriteLine("Public field set retriever without cast: {0}", _stopWatch.ElapsedMilliseconds);

这将导致下面的文字。

Public field set retriever with cast: 751
Public field set retriever without cast: 475

如您所见,差异是显而易见的。可能不是使用相同的方法,而是使用参数强制转换/不进行强制转换,通过创建将参数强制转换/转换为适当类型的新方法来创建正确的委托。

好吧,我们将使用FieldSet2方法而不是FieldSet重载,但相应地将它们重命名为FieldSetFieldSetWithCast 

改进

如果你想知道字段中的readonly关键字,这是一件好事。这件事情应该以类似的方式覆盖,比如null检查属性中的gettersetter方法。毕竟,readonly字段与get-only属性类似。有没有办法找出是否使用readonly关键字声明字段?您肯定记得,readonly字段可以在字段声明或构造函数中设置。两种方式都在对象初始化时执行。FieldInfo类型有名称为IsInitOnly的属性该属性指示字段是可以随时设置还是仅在初始化期间设置——这是判断字段是否为只读的另一种方法。这意味着,我们也需要检查这个属性,而不只是在fieldinfo变量中检查空值。在两个字段中:静态字段和实例字段。

if (fieldInfo != null && !fieldInfo.IsInitOnly)

当然,在所有StaticFieldSetFieldSet方法中都需要它,即

public static Action<object, TProperty> FieldSet<TProperty>(this Type source, string fieldName)
{
    var fieldInfo = GetFieldInfo(source, fieldName);
    if (fieldInfo != null && !fieldInfo.IsInitOnly)
    {
        var sourceParam = Expression.Parameter(typeof(object));
        var valueParam = Expression.Parameter(typeof(TProperty));
        var te = Expression.Lambda(typeof(Action<object, TProperty>),
            Expression.Assign(Expression.Field(Expression.Convert(sourceParam, source), fieldInfo), valueParam),
            sourceParam, valueParam);
        return (Action<object, TProperty>)te.Compile();
    }
    return null;
}

总结

现在我们有了一种从类型中按字段和属性(静态和实例)检索数据的方法。我们还介绍了作为特殊实例属性的索引器。剩下的是事件(实例不是静态的)、方法和构造函数。这些类型的成员将在下一篇文章中介绍。

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

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

 

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

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值