循环中的有效反射

本文关键字:反射 有效 循环 | 更新日期: 2023-09-27 18:18:04

在循环中使用反射时有性能问题。问题是我使用它来重复访问长依赖链末端的对象。例如,在这样的情况下

class FirstObject 
{
    public SecondObject sO;
}
class SecondObject
{
    public ThirdObject tO;
}
class ThirdObject
{
    public FourthObject fO;
}
class FourthObject
{
    public object neededValue;
}

因为我只对最后一个对象包含的值感兴趣,所以我需要用GetProperty().GetValue()

重复遍历整个链

FirstObject -> SecondObject -> ThirdObject -> FourthObject [needdedvalue]

在这种情况下,是否有任何方法,也许是一些API,可以用来缩短链或只是保存整个路径到neededValue ?

澄清

我需要对包含FirstObjects的列表执行此操作。我不能重写代码来减少嵌套级别:它是自动生成的。

循环中的有效反射

你可以用一个技巧来代替反射的GetValue()。它确实更快,但代码的可读性会差得多。

object GetPropertyValue(object obj, string propertyName)
{
    MethodInfo propertyGetter = obj.GetType().GetMethod("get_" + propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
    Func<object> getPropertyValue = (Func<object>)Delegate.CreateDelegate(typeof(Func<object>), obj, propertyGetter);
    return getPropertyValue();
}

在这里,我们使用了一点"技巧",知道属性getter只是一个具有预定义名称的方法:get_<PropertyName>()

你甚至可以缓存上面例子中的propertyGetter对象并重用它,如果你的对象层次结构每次都是相同的。


更新

你甚至可以在没有具体对象引用的情况下为属性getter创建委托。因此,您可以将此委托用于许多对象(相同类型):

Func<ObjectType, object> getPropertyValue = (Func<ObjectType, object>)Delegate.CreateDelegate(typeof(Func<ObjectType, object>), propertyGetter);
ObjectType obj;
var propertyValue = getPropertyValue(obj);

如果缓存getPropertyValue()委托,那么性能将明显优于调用GetValue()方法。

假设您知道根对象的类型和您感兴趣的成员的路径,您可以在循环外部准备一个Func<object, object>委托并在循环内部使用它。这样,您将消除GetProperty/GetFieldGetValue反射成本。

准备这种委托的最简单的方法是使用System.Linq.Expressions.Expression方法构建和编译lambda表达式:

public static class SelectorFactory
{    
    public static Func<object, object> GetSelector(Type type, string memberPath)
    {
        return CreateSelector(type, memberPath);
    }
    static Func<object, object> CreateSelector(Type type, string memberPath)
    {
        var parameter = Expression.Parameter(typeof(object), "source");
        var source = Expression.Convert(parameter, type);
        var value = memberPath.Split('.').Aggregate(
            (Expression)source, Expression.PropertyOrField);
        if (value.Type.IsValueType)
            value = Expression.Convert(value, typeof(object));
        // (object source) => (object)((T)source).Prop1.Prop2...PropN
        var selector = Expression.Lambda<Func<object, object>>(value, parameter);
        return selector.Compile();
    }
}

用你的例子测试:

// This would be outside of the loop
var selector = SelectorFactory.GetSelector(typeof(FirstObject), "sO.tO.fO.neededValue");
// and this inside (of course instead of new you would get item from a list)
var item = new FirstObject { sO = new SecondObject { tO = new ThirdObject { fO = new FourthObject { neededValue = "Ivan" } } } };
var value = selector(item);

注:如果您想知道为什么我使用了两个帮助器方法(一个是公共的,一个是私有的),那是因为在某些时候您可以很容易地添加选择器缓存,例如添加字典并只更改公共方法实现,就像这样:

static readonly Dictionary<Tuple<Type, string>, Func<object, object>> selectorCache = new Dictionary<Tuple<Type, string>, Func<object, object>>();
public static Func<object, object> GetSelector(Type type, string memberPath)
{
    var key = Tuple.Create(type, memberPath);
    Func<object, object> value;
    lock (selectorCache)
    {
        if (!selectorCache.TryGetValue(key, out value))
            selectorCache.Add(key, value = CreateSelector(type, memberPath));
    }
    return value;
}