.net Framework 3.5 + C# 3 发布了包括LinQ等一系列功能,其中包括了匿名类型,而我们在升级到.net4后,发现原来写好的用于POCO的深拷贝方法 static object Clone(object obj) 在匿名对象上不管用了。
目前使用的深拷贝实现方式包括:
上述方式均不可用,考察原因,我们使用.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(); }
得到:
从反编译的代码,我们可以看到,匿名类型仅有一个构造函数,而该构造函数的参数和其属性是一一对应的,查看其代码,发现其正式通过此构造函数为各个域赋值的,我们便从从这个点入手考虑深拷贝的实现。
现在将匿名类型和非匿名类型的深拷分开处理,这里我们将原来的深拷贝方法重命名为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提供了直接的方式来判定类型是否是匿名类型,目前只能通过类型的特征来判断,从匿名类型的结构上抽取这些特征:
根据这些特征,编写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; }
下面是完整的代码:
logs_code_hide('c189c519-9bf5-43aa-82f8-d453dee35971',event)" src="/Upload/Images/2013112023/2B1B950FA3DF188F.gif" alt="" />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
该方案的缺点显而易见:它是根据匿名类型的编译结果分析得到的,依赖于编译器的实现,一旦编译结果改变,方案可能就不管用了。
两个疑问: