[C#]匿名类型的深拷贝
.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#代码来。
[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()[].GetParameters();
var args = new object[parameters.Length]; // 对应构造函数的每个参数,取同名属性的值
for (int i = ; 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()[].GetParameters();
var args = new object[parameters.Length]; for (int i = ; 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方法
}
简单的测试:
var o = new { Foo = , Bar = "x" };
dynamic cloned = Clone(o);
Console.WriteLine("{0} {1}", cloned.Foo, cloned.Bar); //=> 3 x
var o2 = new { Foo = "x", Bar = };
dynamic cloned2 = Clone(o2);
Console.WriteLine("{0} {1}", cloned2.Foo, cloned2.Bar); //=> x 3
小结
该方案的缺点显而易见:它是根据匿名类型的编译结果分析得到的,依赖于编译器的实现,一旦编译结果改变,方案可能就不管用了。
写在后面
两个疑问:
- 此问题来自于一次对于匿名类型的不太正确的使用,该场景随后被改进,于是不再需要拷贝匿名对象了,但留下了如何进行拷贝的问题。那么到底在什么场景下才需要用到匿名类型的拷贝呢?
- 如果用Mono.Cecil给匿名类型加上SerialiableAttribute,其实例是否可用BinaryFormatter进行序列化和反序列化,进而通过这种方式实现Clone呢?
[C#]匿名类型的深拷贝的更多相关文章
- Entity Framework 6 Recipes 2nd Edition(11-5)译 -> 从”模型定义”函数返回一个匿名类型
11-5. 从”模型定义”函数返回一个匿名类型 问题 想创建一个返回一个匿名类型的”模型定义”函数 解决方案 假设已有游客(Visitor) 预订(reservation)房间(hotel ) 的模型 ...
- Linq专题之提高编码效率—— 第二篇 神一样的匿名类型
说起匿名类型,我们都知道这玩意都是为linq而生,而且匿名类型给我们带来的便利性大家在实战中应该都体会到了,特别适合于一次性使用,临时 使用这些场景,虽然说是匿名类型,也就是说是有类型的,只是匿名了而 ...
- 《精通C#》自定义类型转化-扩展方法-匿名类型-指针类型(11.3-11.6)
1.类型转化在C#中有很多,常用的是int类型转string等,这些都有微软给我们定义好的,我们需要的时候直接调用就是了,这是值类型中的转化,有时候我们还会需要类类型(包括结构struct)的转化,还 ...
- 当匿名类型遇上Distinct
首先定义一个简单类,并重写ToString方法. public class CommidityFilter { public string Property { get; set; } public ...
- C#简单问题,不简单的原理:不能局部定义自定义类型(不含匿名类型)
今天在进行代码测试时发现,尝试在一个方法中定义一个委托,注意是定义一个委托,而不是声明一个委托变量,在编写的时候没有报错,VS也能智能提示,但在编译时却报语法不完整,缺少方括号,但实际查询并没有缺少, ...
- JavaScriptSerializer 中的匿名类型 转json
二:JavaScriptSerializer 中的匿名类型 这个类型我想大家都清楚,不过性能更高的方式应该是用JsonConvert吧,但这个不是本篇讨论的话题,我们重点来看看匿名类型的Json序列化 ...
- 使用ExposedObject对Asp.net MVC中匿名类型的JsonResult做单元测试
返回JsonResult是MVC中的常见返回值类型,而且简单方便的方式是结合匿名类型一起使用. 比如: public ActionResult PreviewEmail() { …… return J ...
- 15.C#回顾及匿名类型(八章8.1-8.5)
今天的篇幅应该会很长,除了回顾前面学的一些,还有写一些关于匿名类型的相关知识,总体上对后续的学习很有帮助,学好了,后面更容易理解,不明白的,那就前面多翻几次,看多了总是会理解的.那么,进入正题吧. 自 ...
- 编写高质量代码改善C#程序的157个建议[匿名类型、Lambda、延迟求值和主动求值]
前言 从.NET3.0开始,C#开始一直支持一个新特性:匿名类型.匿名类型由var.赋值运算符和一个非空初始值(或以new开头的初始化项)组成.匿名类型有如下基本特性: 1.既支持简单类型也支持复杂类 ...
随机推荐
- Builder模式在Java中的应用(转)
在设计模式中对Builder模式的定义是用于构建复杂对象的一种模式,所构建的对象往往需要多步初始化或赋值才能完成.那么,在实际的开发过程中,我们哪些地方适合用到Builder模式呢?其中使用Build ...
- Java [Leetcode 219]Contains Duplicate II
题目描述: Given an array of integers and an integer k, find out whether there are two distinct indices i ...
- Java [Leetcode 263]Ugly Number
题目描述: Write a program to check whether a given number is an ugly number. Ugly numbers are positive n ...
- 【C#学习笔记】smtp发邮件
using System; using System.Net; using System.Net.Mail; using System.Text; namespace ConsoleApplicati ...
- 【转】IOS NSTimer 定时器用法总结
原文网址:http://my.oschina.net/u/2340880/blog/398598 NSTimer在IOS开发中会经常用到,尤其是小型游戏,然而对于初学者时常会注意不到其中的内存释放问题 ...
- 多线程监控文件夹,FlieSystemWatcher,并使用共享函数
发表于: 2011-01-06 09:55:47 C# code ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 2 ...
- [Everyday Mathematics]20150119
设 $V$ 是 $n$ 维线性空间, $V_1, V_2$ 均为 $V$ 的子空间, 且 $$\bex V_1\subset V_2,\quad \dim V=10,\quad \dim V_1=3, ...
- window下版本控制工具Git 客户端安装
安装使用 1.下载msysgit http://code.google.com/p/msysgit/ 2.下载tortoisegit客户端安装 http://code.google.com/p/tor ...
- HDU 4267-A Simple Problem with Integers(多个BIT)
题意: 2种操作 1 a b k c 在区间[a,b]中的(i-a)%k==0的位置i上的数+c 2 a 查询位置a的值 输出每次查询的值 分析: 开始想到多维的线段树,但比较麻烦,看了题解才知道,用 ...
- sf空间配置
1.创建VHost 记住Homepage,打开VHost DNS标签页,创建Virtual Host,如下图: 2.Wcp上传文件 用户名是"sf用户名,sf项目名" ...