使用反射+缓存+委托,实现一个不同对象之间同名同类型属性值的快速拷贝
为了让这个“*”尽量有实用价值,效率肯定是需要考虑的,所以决定采用“反射+缓存+委托”的路子。
第一次使用,肯定要反射出来对象的属性,这个简单,就下面的代码:
type targettype;
//....
propertyinfo[] targetproperties = targettype.getproperties(bindingflags.public | bindingflags.instance);
这里只获取公开的实例对象的属性。
要实现同名同类型的属性拷贝,那么需要把这些属性找出来,下面是完整的代码:
public modulecast(type sourcetype, type targettype)
{
propertyinfo[] targetproperties = targettype.getproperties(bindingflags.public | bindingflags.instance);
foreach (propertyinfo sp in sourcetype.getproperties(bindingflags.public | bindingflags.instance))
{
foreach (propertyinfo tp in targetproperties)
{
if (sp.name == tp.name && sp.propertytype == tp.propertytype)
{
castproperty cp = new castproperty();
cp.sourceproperty = new propertyaccessorhandler(sp);
cp.targetproperty = new propertyaccessorhandler(tp);
mproperties.add(cp);
break;
}
}
}
}
这里使用了一个 castproperty 类来保存要处理的源对象和目标对象,并且把这组对象放到一个castproperty 列表的mproperties 静态对象里面缓存起来。
下面是 castproperty 类的定义:
/// <summary>
/// 转换属性对象
/// </summary>
public class castproperty
{
public propertyaccessorhandler sourceproperty
{
get;
set;
}
public propertyaccessorhandler targetproperty
{
get;
set;
}
}
类本身很简单,关键就是这个属性访问器propertyaccessorhandler 对象,下面是它的定义:
/// <summary>
/// 属性访问器
/// </summary>
public class propertyaccessorhandler
{
public propertyaccessorhandler(propertyinfo propinfo)
{
this.propertyname = propinfo.name;
//var obj = activator.createinstance(classtype);
//var gettertype = typeof(fastpropertyaccessor.getpropertyvalue<>).makegenerictype(propinfo.propertytype);
//var settertype = typeof(fastpropertyaccessor.setpropertyvalue<>).makegenerictype(propinfo.propertytype);
//this.getter = delegate.createdelegate(gettertype, null, propinfo.getgetmethod());
//this.setter = delegate.createdelegate(settertype, null, propinfo.getsetmethod());
if (propinfo.canread)
this.getter = propinfo.getvalue;
if (propinfo.canwrite)
this.setter = propinfo.setvalue;
}
public string propertyname { get; set; }
public func<object, object[], object> getter { get; private set; }
public action<object, object, object[]> setter { get; private set; }
}
在写这个类的时候,曾经走了好几次弯路,前期准备通过 delegate.createdelegate 方式创建一个当前属性get和set方法的委托,但是经过数次测试发现,
delegate.createdelegate(gettertype, obj, propinfo.getgetmethod());
这里的obj 要么是一个对象实例,要么是null,如果是null,那么这个委托定义只能绑定到类型的静态属性方法上;如果不是null,那么这个委托只能绑定到当前 obj 实例对象上,换句话说,如果将来用obj类型的另外一个实例对象,那么这个委托访问的还是之前那个obj 对象,跟新对象实例无关。
ps:为了走这条“弯路”,前几天还特意写了一个fastpropertyaccessor,申明了2个泛型委托,来绑定属性的get和set方法,即上面注释掉的2行代码:
var gettertype = typeof(fastpropertyaccessor.getpropertyvalue<>).makegenerictype(propinfo.propertytype);
var settertype = typeof(fastpropertyaccessor.setpropertyvalue<>).makegenerictype(propinfo.propertytype);
好不容易将这个泛型委托创建出来了,编译也通过了,却发现最终没法使用,别提有多郁闷了:-《
回归话题,有了propertyaccessorhandler,那么我们只需要遍历当前要转换的目标类型的属性集合,就可以开始对属性进行拷贝了:
public void cast(object source, object target)
{
if (source == null)
throw new argumentnullexception("source");
if (target == null)
throw new argumentnullexception("target");
for (int i = 0; i < mproperties.count; i++)
{
castproperty cp = mproperties[i];
if (cp.sourceproperty.getter != null)
{
object value = cp.sourceproperty.getter(source, null); //propertyinfo.getvalue(source,null);
if (cp.targetproperty.setter != null)
cp.targetproperty.setter(target, value, null);// propertyinfo.setvalue(target,value ,null);
}
}
}
上面的代码会判断属性的set访问器是否可用,可用的话才复制值,所以可以解决“只读属性”的问题。
注意:这里只是直接复制了属性的值,对应的引用类型而言自然也只是复制了属性的引用,所以这是一个“浅表拷贝”。
现在,主要的代码都有了,因为我们缓存了执行类型对象的属性访问方法的委托,所以我们的这个“属性值拷贝程序”具有很高的效率,有关委托的效率测试,在前一篇
《使用泛型委托,构筑最快的通用属性访问器》 https://www.cnblogs.com/bluedoctor/archive/2012/12/18/2823325.html
已经做了测试,大家可以去看看测试结果,缓存后的委托方法,效率非常高的。
为了让该小程序更好用,又写了个扩展方法,让object类型的对象都可以方便的进行属性值拷贝
/// <summary>
/// 对象转换扩展
/// </summary>
public static class modulecastextension
{
/// <summary>
/// 将当前对象的属性值复制到目标对象,使用浅表复制
/// </summary>
/// <typeparam name="t">目标对象类型</typeparam>
/// <param name="source">源对象</param>
/// <param name="target">目标对象,如果为空,将生成一个</param>
/// <returns>复制过后的目标对象</returns>
public static t copyto<t>(this object source, t target = null) where t : class,new()
{
if (source == null)
throw new argumentnullexception("source");
if (target == null)
target = new t();
modulecast.getcast(source.gettype(), typeof(t)).cast(source, target);
return target;
}
}
这样,该小程序可以象下面以几种不同的形式来使用了:
// 下面几种用法一样:
modulecast.getcast(typeof(carinfo), typeof(implcarinfo)).cast(info, ic);
modulecast.castobject<carinfo, implcarinfo>(info, ic);
modulecast.castobject(info, ic);
implcarinfo icresult= info.copyto<implcarinfo>(null);
implcarinfo icresult2 = new implcarinfo();
info.copyto<implcarinfo>(icresult2);
完整的代码下载,请看这里。
补充:
经网友使用发现,需要增加一些不能拷贝的属性功能,下面我简单的改写了下原来的代码(这些代码没有包括在上面的下载中):
/// <summary>
/// 将源类型的属性值转换给目标类型同名的属性
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
public void cast(object source, object target)
{
cast(source, target, null);
}
/// <summary>
/// 将源类型的属性值转换给目标类型同名的属性,排除要过滤的属性名称
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
/// <param name="filter">要过滤的属性名称</param>
public void cast(object source, object target,string[] filter)
{
if (source == null)
throw new argumentnullexception("source");
if (target == null)
throw new argumentnullexception("target");
for (int i = 0; i < mproperties.count; i++)
{
castproperty cp = mproperties[i];
if (cp.sourceproperty.getter != null)
{
object value = cp.sourceproperty.getter(source, null); //propertyinfo.getvalue(source,null);
if (cp.targetproperty.setter != null)
{
if (filter == null)
cp.targetproperty.setter(target, value, null);
else if (!filter.contains(cp.targetproperty.propertyname))
cp.targetproperty.setter(target, value, null);
}
}
}
}
然后这修改一下那个扩展方法:
public static t copyto<t>(this object source, t target = null,string[] filter=null) where t : class,new()
{
if (source == null)
throw new argumentnullexception("source");
if (target == null)
target = new t();
modulecast.getcast(source.gettype(), typeof(t)).cast(source, target, filter);
return target;
}
最后,这样调用即可:
class program
{
static void main(string[] args)
{
a a = new a() { name="aaa", nocopyname="no.no.no."};
var b = a.copyto<b>(filter: new string[] { "nocopyname" });
} www.2cto.com
}
class a
{
public string name { get; set; }
public string nocopyname { get; set; }
public datetime gettime { get { return datetime.now; } }
}
class b
{
public string name { get; set; }
public string nocopyname { get; set; }
public datetime gettime { get { return datetime.now; } }
}