在循环中使用反射时有性能问题。问题是我使用它来重复访问长依赖链末端的对象。例如,在这样的情况下
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
/GetField
和GetValue
反射成本。
准备这种委托的最简单的方法是使用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;
}