如何为可以处理未知类型的函数创建lambda表达式?对不起,我知道问题很模糊,我很难形成它。我只能希望你有一分钟时间阅读我的故事,这应该会让事情变得清晰起来。
我的目标是使用预定义的数据协定将字符串值数组反序列化为对象。数据合同的成员有一个位置编号。反序列化器的简单工作是将值映射到数据成员(在执行适当的类型转换之后),并构建对象。
问题是反序列化性能很糟糕!在运行VS Profiler之后,我发现PropertyInfo.SetValue()是用于填充对象成员的,它占用的时间最多。我的程序必须在任何给定时间反序列化数千个对象。数据合同通常有100个成员。所以我们正在为每1000个对象谈论100,000次调用SetValue(),并且它正在拖动。以下是对SetValue的调用示例:
// for each data contract type
// go through each property and set the value
foreach(PropertyInfo pi in pis)
{
object data = convertStringToMemberType(pi, attributeArray, valueStringArray);
pi.SetValue(objectToBuild, data, null);
}
然后我找到了this page from Unknown Recipes,它有一个很有希望解决这个性能问题的方法。 看起来我需要使用已编译的lambda表达式来替换SetValue,但我遇到了一个问题。按照上面的链接示例,我现在可以替换SetValue()。替换是Action委托,它是已编译的lambda表达式。
首先,我扩展了PropertyInfo类。
public static class PropertyInfoExtensions
{
public static Action<object, object> GetValueSetter(this PropertyInfo propertyInfo)
{
var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
var argument = Expression.Parameter(typeof(object), "a");
var setterCall = Expression.Call(
instance,
propertyInfo.GetSetMethod(),
Expression.Convert(argument, propertyInfo.PropertyType));
return (Action<object, object>)Expression.Lambda(setterCall, instance, argument).Compile();
}
}
然后我构建了一个Dictionary<PropertyInfo, Action<object, object>
对象,它将每个propertyInfo对象绑定到相应的Action委托。这样我就可以“缓存”已编译的lambda并在一批反序列化中重用它。这就是我现在称之为:
foreach(PropertyInfo pi in pis)
{
object data = convertStringToMemberType(pi, attributeArray, valueStringArray);
var setValueDelegate = _actionDelegateDict[pi];
setValueDelegate(objectToBuild, data);
}
但是,我收到以下异常:
Unable to cast object of type 'System.Action`2[Test.DataContract1,System.Object]' to type 'System.Action`2[System.Object,System.Object]'.
这里DataContract1是我正在尝试构建的对象的类型。它仅在运行时才知道,这与Unknown Recipes中的示例中的场景不同,其中类型在编译时是已知的。你将如何使这个lambda表达式工作?
非常感谢你的时间!
答案 0 :(得分:4)
想象一下,你是直接创建这个lambda,而不是使用Expression
。那会是什么样子?您想要创建Action<object, object>
:
Action<object, object> action = (object i, object a) => i.Property = a;
但这不起作用,您需要同时投射i
和a
。所以:
Action<object, object> action =
(object i, object a) => ((DataContract)i).Property = (PropertyType)a;
在您的代码中,您正在投射a
,但您也需要投射i
:
public static Action<object, object> GetValueSetter(this PropertyInfo propertyInfo)
{
var instance = Expression.Parameter(typeof(object), "i");
var argument = Expression.Parameter(typeof(object), "a");
var setterCall = Expression.Call(
Expression.Convert(instance, propertyInfo.DeclaringType),
propertyInfo.GetSetMethod(),
Expression.Convert(argument, propertyInfo.PropertyType));
return (Action<object, object>)Expression.Lambda(setterCall, instance, argument).Compile();
}
答案 1 :(得分:3)
听起来和我对FastReflection库的处理方式非常相似。您几乎就在那里,您只需将实例参数更改为对象类型,然后对该表达式执行强制转换为实际类型。
我认为你现在拥有的代码如果改成了这个代码就可以了。
public static class PropertyInfoExtensions
{
public static Action<object, object> GetValueSetter(this PropertyInfo propertyInfo)
{
var instance = Expression.Parameter(typeof(object), "i");
var argument = Expression.Parameter(typeof(object), "a");
var setterCall = Expression.Call(
Expression.Convert(instance, propertyInfo.DeclaringType),
propertyInfo.GetSetMethod(),
Expression.Convert(argument, propertyInfo.PropertyType));
return Expression.Lambda<Action<object,object>>(setterCall, instance, argument).Compile();
}
}