这里提供了一种使用表达式树的方法。首先,定义地图的API:
public class PropertyMap<T> where T : new()
{
public void Add(string sourceName, Expression<Func<T, object>> getProperty);
public T CreateObject(IDictionary<string, object> values);
}
你会这样使用它:
var map = new PropertyMap<ProductViewModel>();
map.Add("Id", p => p.Id);
map.Add("Code", p => p.Code);
map.Add("Description", p => p.Description);
map.Add("Active", p => p.Active);
var productViewModel = map.CreateObject(values);
要实现它,首先需要声明一个字典,将数据源中的名称与属性关联:
private readonly IDictionary<string, PropertyInfo> _properties = new Dictionary<string, PropertyInfo>()
接下来,你需要根据字典实现Add
方法(所有的错误处理都留给读者自己练习):
public void Add(string sourceName, Expression<Func<T, object>> getProperty)
{
_properties[sourceName] = (PropertyInfo) ((MemberExpression) getProperty.Body).Member;
}
然后,您将使用表达式树动态编译一个方法来执行赋值操作(听起来比实际情况要可怕得多)。最简单的方式是查看我们正在构建的示例。我们需要的是一些代码,它可以完成以下操作:
new ProductViewModel
{
Id = ...,
Code = ...,
Description = ...,
Active = ...
}
但是,由于动态映射,我们无法在编译时知道这一点。因此,我们将构建一个函数,该函数是完全相同的代码,但在运行时编译。表达式树只是表示您可以在编译时编写的相同代码的运行时数据。
首先,我们需要获取属性的一组绑定(赋值):
private IEnumerable<MemberBinding> GetPropertyBindings(IDictionary<string, object> values)
{
return
from sourceName in _properties.Keys
select Expression.Bind(_properties[sourceName], Expression.Constant(values[sourceName]));
}
我们在这里说的是,对于映射属性中的每个属性,查找其值并将其作为常量(例如对于
Id
,可能是值7),然后将相应的属性绑定到它。这给我们带来了表达式
Id = 7
。我们对所有属性重复此过程,从而得到所有的赋值语句。
一旦我们有了这些绑定,我们就可以创建完整的成员初始化,包括构造函数调用:
private MemberInitExpression GetMemberInit(IDictionary<string, object> values)
{
return Expression.MemberInit(Expression.New(typeof(T)), GetPropertyBindings(values));
}
因为我们在类声明中指定了
where T : new()
,所以我们保证有一个无参构造函数可以在这里调用。我们传递之前创建的属性绑定,得到了一个表示我们想要构建的初始化表达式的数据结构。
那么现在我们该怎么做呢?我们拥有了这个数据结构,但是如何调用这段代码呢?为了做到这点,我们必须将该表达式包装在一个可调用的函数中,因为你实际上只能
调用方法。这意味着我们正在构建以下代码:
() => new ProductViewModel
{
Id = ...,
Code = ...,
Description = ...,
Active = ...
}
这是一个无参函数,当调用时将返回初始化的对象。这也被称为lambda表达式。我们可以按以下方式获取此数据结构:
private Func<T> GetInitializationFunction(IDictionary<string, object> values)
{
var initializationLambda = Expression.Lambda<Func<T>>(GetMemberInit(values));
return initializationLambda.Compile();
}
我们创建了一个Lambda表达式,其主体是成员初始化,这正是我们上面编写的代码。我们指定委托类型
Func<T>
,因为它不带参数并返回映射类型的对象。
然后,我们编译它。这个调用生成一个具有签名
Func<T>
的方法,我们可以调用它,并且它的主体是我们创建的数据结构作为代码。这是一种不直接使用反射进行反射的巧妙方式。
最后,我们通过创建函数并调用它来实现我们之前定义的
CreateObject
方法,从而给我们一个
T
(在这里是
ProductViewModel
)的实例:
public T CreateObject(IDictionary<string, object> values)
{
var initializationFunction = GetInitializationFunction(values);
return initializationFunction();
}
Func<T>
,就好像你传递了一个自己编写的方法的引用一样。此外,在这里还有丰富的缓存机会;例如,您可能只在第一次添加映射之后生成方法。然后,如果您将PropertyMap<T>
实例缓存到应用程序级别,开销非常小。 - Bryan Watts