.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#]匿名类型的深拷贝的更多相关文章

  1. Entity Framework 6 Recipes 2nd Edition(11-5)译 -> 从”模型定义”函数返回一个匿名类型

    11-5. 从”模型定义”函数返回一个匿名类型 问题 想创建一个返回一个匿名类型的”模型定义”函数 解决方案 假设已有游客(Visitor) 预订(reservation)房间(hotel ) 的模型 ...

  2. Linq专题之提高编码效率—— 第二篇 神一样的匿名类型

    说起匿名类型,我们都知道这玩意都是为linq而生,而且匿名类型给我们带来的便利性大家在实战中应该都体会到了,特别适合于一次性使用,临时 使用这些场景,虽然说是匿名类型,也就是说是有类型的,只是匿名了而 ...

  3. 《精通C#》自定义类型转化-扩展方法-匿名类型-指针类型(11.3-11.6)

    1.类型转化在C#中有很多,常用的是int类型转string等,这些都有微软给我们定义好的,我们需要的时候直接调用就是了,这是值类型中的转化,有时候我们还会需要类类型(包括结构struct)的转化,还 ...

  4. 当匿名类型遇上Distinct

    首先定义一个简单类,并重写ToString方法. public class CommidityFilter { public string Property { get; set; } public ...

  5. C#简单问题,不简单的原理:不能局部定义自定义类型(不含匿名类型)

    今天在进行代码测试时发现,尝试在一个方法中定义一个委托,注意是定义一个委托,而不是声明一个委托变量,在编写的时候没有报错,VS也能智能提示,但在编译时却报语法不完整,缺少方括号,但实际查询并没有缺少, ...

  6. JavaScriptSerializer 中的匿名类型 转json

    二:JavaScriptSerializer 中的匿名类型 这个类型我想大家都清楚,不过性能更高的方式应该是用JsonConvert吧,但这个不是本篇讨论的话题,我们重点来看看匿名类型的Json序列化 ...

  7. 使用ExposedObject对Asp.net MVC中匿名类型的JsonResult做单元测试

    返回JsonResult是MVC中的常见返回值类型,而且简单方便的方式是结合匿名类型一起使用. 比如: public ActionResult PreviewEmail() { …… return J ...

  8. 15.C#回顾及匿名类型(八章8.1-8.5)

    今天的篇幅应该会很长,除了回顾前面学的一些,还有写一些关于匿名类型的相关知识,总体上对后续的学习很有帮助,学好了,后面更容易理解,不明白的,那就前面多翻几次,看多了总是会理解的.那么,进入正题吧. 自 ...

  9. 编写高质量代码改善C#程序的157个建议[匿名类型、Lambda、延迟求值和主动求值]

    前言 从.NET3.0开始,C#开始一直支持一个新特性:匿名类型.匿名类型由var.赋值运算符和一个非空初始值(或以new开头的初始化项)组成.匿名类型有如下基本特性: 1.既支持简单类型也支持复杂类 ...

随机推荐

  1. 在PHP中如何获取用户的真实IP

    /** * 获得用户的真实IP地址 * * @access public * @return string */ function real_ip() { static $realip = NULL; ...

  2. 向Oracle中插入记录时,出现“Oracle.DataAccess.Client.OracleException ORA-00933 ”错误

    错误信息的弹出框

  3. Chapter5:语句

    表达式语句:一个表达式+一个分号 表达式语句的作用是执行表达式并丢弃掉求值结果. 空语句:单独一个分号 Best Practice:使用空语句时应该加上注释,从而令读这段代码的人知道该语句是有意省略的 ...

  4. 《Linux命令行与shell脚本编程大全》 第三章 学习笔记

    第三章:基本的bash shell命令 bash程序使用命令行参数来修改所启动shell的类型 参数 描述 -c string 从string中读取命令并处理他们 -r 启动限制性shell,限制用户 ...

  5. Javascript异步请求你能捕获到异常吗?

    Javascript异步请求你能捕获到异常吗? 异常处理是程序发布之前必须要解决的问题,不经过异常处理的应用会让用户对产品失去信心.在异常处理中,我们一贯的做法是按照函数调用的次序,将异常从数据访问层 ...

  6. [Hive - LanguageManual ] ]SQL Standard Based Hive Authorization

    Status of Hive Authorization before Hive 0.13 SQL Standards Based Hive Authorization (New in Hive 0. ...

  7. Apache Rewrite常用设置说明

    例子: RewriteEngine on 打开引擎 RewriteRule test.html /test.php [L] RewriteRule test.html?$ /tianqi.php?s1 ...

  8. 北京Uber优步司机奖励政策(3月5日)

    滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...

  9. CoffeeScript学习(2)—— 变量

    变量基础 对于变量的定义的话,形式如下所示 xxx = yyy ------编译后------ var xxx = yyy; 保留字 我们知道,在原生js中的保留字是不能作为变量名或者属性名的.如果我 ...

  10. Com进程通信(Delphi2007)

    相关资料: 1.http://my.oschina.net/u/582827/blog/2847662.http://www.cnblogs.com/findumars/p/5277561.html3 ...