[C#]匿名类型的深拷贝
2013-11-20 20:44
197 查看
.net Framework 3.5 + C# 3 发布了包括LinQ等一系列功能,其中包括了匿名类型,而我们在升级到.net4后,发现原来写好的用于POCO的深拷贝方法 static object Clone(object obj) 在匿名对象上不管用了。
在类型内部编码实现,比如实现ICloneable接口。
通过序列化、反序列化方式复制对象。
使用反射遍历被拷贝对象的属性,取值并赋值给新的实例。
上述方式均不可用,考察原因,我们使用.net Reflector反编译匿名类型 new { Foo = 123, Bar = 456 },可见其代码结构如下:
注:编译与运行在.net Framework 4。目前发现使用ILSpy似乎只能看到IL,不能反出C#代码来。
得到:
匿名类型的代码是编译器生成的,所以无法在其内部进行手工编码。
.net内置的几个序列化器要求类型被标记SerializableAttribute,或者实现序列化接口,或者属性可读写且有无参数的构造函数,匿名类型并不符合这些条件。
匿名类型的属性没有set方法,不能通过反射赋值。
从反编译的代码,我们可以看到,匿名类型仅有一个构造函数,而该构造函数的参数和其属性是一一对应的,查看其代码,发现其正式通过此构造函数为各个域赋值的,我们便从从这个点入手考虑深拷贝的实现。
是一个泛型的非公共class;
标记有CompilerGeneratedAttribute;
类名称带有“AnonymousType”。微软编译器编译的类型名称还带有“<>”,但测试Mono编译器的编译结果是没有的,这里取其公共部分。
根据这些特征,编写IsAnonymousType的实现如下:
下面是完整的代码:
View Code
简单的测试:
此问题来自于一次对于匿名类型的不太正确的使用,该场景随后被改进,于是不再需要拷贝匿名对象了,但留下了如何进行拷贝的问题。那么到底在什么场景下才需要用到匿名类型的拷贝呢?
如果用Mono.Cecil给匿名类型加上SerialiableAttribute,其实例是否可用BinaryFormatter进行序列化和反序列化,进而通过这种方式实现Clone呢?
原因与切入点
目前使用的深拷贝实现方式包括:在类型内部编码实现,比如实现ICloneable接口。
通过序列化、反序列化方式复制对象。
使用反射遍历被拷贝对象的属性,取值并赋值给新的实例。
上述方式均不可用,考察原因,我们使用.net Reflector反编译匿名类型 new { Foo = 123, Bar = 456 },可见其代码结构如下:
注:编译与运行在.net Framework 4。目前发现使用ILSpy似乎只能看到IL,不能反出C#代码来。
[CompilerGenerated] internal sealed class <>f__AnonymousType0<<Foo>j__TPar, <Bar>j__TPar> { // Fields [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly <Bar>j__TPar <Bar>i__Field; [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly <Foo>j__TPar <Foo>i__Field; // Constructor [DebuggerHidden] public <>f__AnonymousType0(<Foo>j__TPar Foo, <Bar>j__TPar Bar); // Properties public <Bar>j__TPar Bar { get; } public <Foo>j__TPar Foo { get; } // Methods [DebuggerHidden] public override bool Equals(object value); [DebuggerHidden] public override int GetHashCode(); [DebuggerHidden] public override string ToString(); }
得到:
匿名类型的代码是编译器生成的,所以无法在其内部进行手工编码。
.net内置的几个序列化器要求类型被标记SerializableAttribute,或者实现序列化接口,或者属性可读写且有无参数的构造函数,匿名类型并不符合这些条件。
匿名类型的属性没有set方法,不能通过反射赋值。
从反编译的代码,我们可以看到,匿名类型仅有一个构造函数,而该构造函数的参数和其属性是一一对应的,查看其代码,发现其正式通过此构造函数为各个域赋值的,我们便从从这个点入手考虑深拷贝的实现。
解决方案
现在将匿名类型和非匿名类型的深拷分开处理,这里我们将原来的深拷贝方法重命名为CloneOnymousObject,而匿名类型的深拷贝方法为CloneAnonymousObject,那么现在的Clone方法如下:static object Clone(object obj) { if (obj == null) return null; if (IsAnonymousType(obj.GetType())) return CloneAnonymousObject(obj); return CloneOnymousObject(obj); }
如何判断类型是匿名类型
并没有发现.net Framework提供了直接的方式来判定类型是否是匿名类型,目前只能通过类型的特征来判断,从匿名类型的结构上抽取这些特征:是一个泛型的非公共class;
标记有CompilerGeneratedAttribute;
类名称带有“AnonymousType”。微软编译器编译的类型名称还带有“<>”,但测试Mono编译器的编译结果是没有的,这里取其公共部分。
根据这些特征,编写IsAnonymousType的实现如下:
private static bool IsAnonymousType(Type type) { if (!type.IsGenericType) return false; if ((type.Attributes & TypeAttributes.NotPublic) != TypeAttributes.NotPublic) return false; if (!Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false)) return false; return type.Name.Contains("AnonymousType"); }
深拷贝的实现
我们要做的便是从被拷贝对象的属性获取对应的值,将其作为新对象的构造函数的参数。而观察匿名类型的结构,可知其构造函数的参数的类型与参数名称其属性的定义是一致的,于是有了下面的方法:private static object CloneAnonymousObject(object obj) { var type = obj.GetType(); var parameters = type.GetConstructors()[0].GetParameters(); var args = new object[parameters.Length]; // 对应构造函数的每个参数,取同名属性的值 for (int i = 0; i < parameters.Length; i++) { var propertyInfo = type.GetProperty(parameters[i].Name); var value = propertyInfo.GetValue(obj, null); args[i] = Clone(value); } var instance = Activator.CreateInstance(type, args); return instance; }
下面是完整的代码:
public static object Clone(object obj) { if (obj == null) return null; if (IsAnonymousType(obj.GetType())) return CloneAnonymousObject(obj); return CloneOnymousObject(obj); }
private static bool IsAnonymousType(Type type) { if (!type.IsGenericType) return false; if ((type.Attributes & TypeAttributes.NotPublic) != TypeAttributes.NotPublic) return false; if (!Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false)) return false; return type.Name.Contains("AnonymousType"); }
private static object CloneAnonymousObject(object obj)
{
var type = obj.GetType();
var parameters = type.GetConstructors()[0].GetParameters();
var args = new object[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
{
var propertyInfo = type.GetProperty(parameters[i].Name);
var value = propertyInfo.GetValue(obj, null);
args[i] = Clone(value);
}
var instance = Activator.CreateInstance(type, args);
return instance;
}
private static object CloneOnymousObject(object obj)
{
//原来的Clone方法
}
View Code
简单的测试:
var o = new { Foo = 3, Bar = "x" }; dynamic cloned = Clone(o); Console.WriteLine("{0} {1}", cloned.Foo, cloned.Bar); //=> 3 x var o2 = new { Foo = "x", Bar = 1 }; dynamic cloned2 = Clone(o2); Console.WriteLine("{0} {1}", cloned2.Foo, cloned2.Bar); //=> x 3
小结
该方案的缺点显而易见:它是根据匿名类型的编译结果分析得到的,依赖于编译器的实现,一旦编译结果改变,方案可能就不管用了。写在后面
两个疑问:此问题来自于一次对于匿名类型的不太正确的使用,该场景随后被改进,于是不再需要拷贝匿名对象了,但留下了如何进行拷贝的问题。那么到底在什么场景下才需要用到匿名类型的拷贝呢?
如果用Mono.Cecil给匿名类型加上SerialiableAttribute,其实例是否可用BinaryFormatter进行序列化和反序列化,进而通过这种方式实现Clone呢?
相关文章推荐
- C#匿名类型及Lambda方法
- C#中的匿名类型与隐式类型变量
- C#:匿名类型
- C#超级有用的一种类型—匿名类型
- C# 3.0 新特性 学习(二):匿名类型、扩展方法
- C#的不足(1):匿名类型的不足之处
- c#匿名类型
- C# 匿名类型序列化、反序列化
- C#实现int类型数组拷贝
- C#特性 匿名类型与隐式类型局部变量使用介绍
- C# 匿名类型var
- C#==>匿名类型
- C#匿名类型的的实际应用
- 《CLR Via C# 第3版》笔记之(十一) - 匿名类型和元组
- C#匿名方法与Delegate类型转换错误
- 匿名类型(C# 编程指南)
- [C#基础知识]专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C#匿名类型
- 编写高质量代码改善C#程序的157个建议[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
- .net中String是引用类型还是值类型 以及 C#深层拷贝浅层拷贝