C# 3.0 中的通用深拷贝方法






4.71/5 (5投票s)
一种在 C# 中使用代理和序列化格式化器深拷贝对象的方法
引言
最近有人要求我为未标记为可序列化的对象提供序列化机制。当使用 SurrogateSelectors
时,解决方案非常简单。此解决方案也对提供深拷贝实用程序很有用。
解决方案
如上所述,所需的就是一个 SurrogateSelector
。
.NET Remoting 框架有许多扩展点。通过使用 ISerializationSurrogate
,我们可以创建一个对象,该对象可以告诉格式化器应该序列化哪种类型的对象。
ISerializationSurrogate
提供以下方法
名称 | 描述 |
GetObjectData | 使用序列化对象所需的数据填充提供的 SerializationInfo。 |
SetObjectData | 使用 SerializationInfo 中的信息填充对象。 |
我们通过反射要序列化的对象来实现此方法。
public void GetObjectData(object obj,
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
{
FieldInfo[] fieldInfos = obj.GetType().GetFields(BindingFlags.Instance |
BindingFlags.Public | BindingFlags.NonPublic);
foreach (var fi in fieldInfos)
{
if (IsKnownType(fi.FieldType)
)
{
info.AddValue(fi.Name, fi.GetValue(obj));
}
else
if (fi.FieldType.IsClass)
{
info.AddValue(fi.Name, fi.GetValue(obj));
}
}
}
我所做的就是获取所有字段并调用 info.AddValue
。保存对象的信息是容易的部分。棘手的部分是检索它。我称之为棘手,因为我们需要特别注意可空类型。我将向您展示实现,并尝试对其进行一些扩展。
public object SetObjectData(object obj,
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context,
System.Runtime.Serialization.ISurrogateSelector selector)
{
FieldInfo[] fieldInfos = obj.GetType().GetFields(BindingFlags.Instance |
BindingFlags.Public | BindingFlags.NonPublic);
foreach (var fi in fieldInfos)
{
if (IsKnownType(fi.FieldType))
{
//var value = info.GetValue(fi.Name, fi.FieldType);
if (IsNullableType(fi.FieldType))
{
// Nullable<argumentValue>
Type argumentValueForTheNullableType = GetFirstArgumentOfGenericType(
fi.FieldType);//fi.FieldType.GetGenericArguments()[0];
fi.SetValue(obj, info.GetValue(fi.Name, argumentValueForTheNullableType));
}
else
{
fi.SetValue(obj, info.GetValue(fi.Name, fi.FieldType));
}
}
else
if (fi.FieldType.IsClass)
{
fi.SetValue(obj, info.GetValue(fi.Name, fi.FieldType));
}
}
return obj;
private Type GetFirstArgumentOfGenericType(Type type)
{
return type.GetGenericArguments()[0];
}
private bool IsNullableType(Type type)
{
if (type.IsGenericType)
return type.GetGenericTypeDefinition() == typeof(Nullable<>);
return false;
}
最后两个函数的作用是:
- 确定类型是否是 Nullable<> 的扩展
默认情况下,当我们编写 int? 时,编译器会在后台为我们生成一个 Nullable<int>。
- 获取
Nullable<>
实例中包含的内容的类型信息,并从格式化器传递给我们的serializationInfo
对象加载其值。
从 SetObjectData
的实现中可以看到,我像以前一样使用反射来检索存储在 SerializationInfo
对象中的值。这样,我们应该也能够反序列化任何没有 SerializableAttribute
的对象。
现在我们需要一种方法来为所有类型的对象使用我们的代理。因此,我们需要一种方法来选择我们代理选择器的所有类型的实现,而无需显式指定它。我们的做法是通过 ISurrogateSelector
。 ISurrogateSelector
接口公开以下方法
方法
名称 | 描述 |
ChainSelector | 如果当前实例没有指定类型、程序集和上下文的代理,则指定下一个 ISurrogateSelector 以供代理检查。 |
GetNextSelector | 返回链中的下一个代理选择器。 |
GetSurrogate | 查找表示指定对象类型的代理,从指定代理选择器开始,用于指定的序列化上下文。 |
以下代码段显示了我如何选择实现此接口。
#region ISurrogateSelector Members
// This is what we'll use to hold the _nextSelector in the chain
System.Runtime.Serialization.ISurrogateSelector _nextSelector;
/// <summary>
/// Sets the selector
/// </summary>
public void ChainSelector(
System.Runtime.Serialization.ISurrogateSelector selector)
{
this._nextSelector = selector;
}
/// <summary>
/// Gets the next selectr from the chain
/// </summary>
public System.Runtime.Serialization.ISurrogateSelector GetNextSelector()
{
return _nextSelector;
}
现在我们需要选择我们要序列化的对象。 IsKnownType
基本是询问对象是否应用了 SerializableAttribute
,或者您不想保存的任何已知类型。
GetSurrogate
方法的下一部分选择此类的实例作为序列化选择器。如果返回值设置为 null,则格式化器(无论是 Soap、Binary 还是其他开源实现,如 OpenNX 或 CompactFrameworkFormmater
)都会选择链中的下一个选择器。
public System.Runtime.Serialization.ISerializationSurrogate GetSurrogate(
Type type, System.Runtime.Serialization.StreamingContext context,
out System.Runtime.Serialization.ISurrogateSelector selector)
{
if (IsKnownType(type))
{
selector = null;
return null;
}
else if (type.IsClass || type.IsValueType)
{
selector = this;
return this;
}
else
{
selector = null;
return null;
}
}
#endregion
以下是 NonSerialiazableTypeSurrogateSelector
的完整代码,我一直在解释它。
/// <summary>
/// This class offers the ability to save the fields
/// of types that don't have the <c ref="System.SerializableAttribute">
/// SerializableAttribute</c>.
/// </summary>
public class NonSerialiazableTypeSurrogateSelector :
System.Runtime.Serialization.ISerializationSurrogate,
System.Runtime.Serialization.ISurrogateSelector
{
#region ISerializationSurrogate Members
public void GetObjectData(object obj,
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
{
FieldInfo[] fieldInfos = obj.GetType().GetFields(BindingFlags.Instance |
BindingFlags.Public | BindingFlags.NonPublic);
foreach (var fi in fieldInfos)
{
if (IsKnownType(fi.FieldType)
)
{
info.AddValue(fi.Name, fi.GetValue(obj));
}
else
if (fi.FieldType.IsClass)
{
info.AddValue(fi.Name, fi.GetValue(obj));
}
}
}
public object SetObjectData(object obj,
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context,
System.Runtime.Serialization.ISurrogateSelector selector)
{
FieldInfo[] fieldInfos = obj.GetType().GetFields(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var fi in fieldInfos)
{
if (IsKnownType(fi.FieldType))
{
//var value = info.GetValue(fi.Name, fi.FieldType);
if (IsNullableType(fi.FieldType))
{
// Nullable<argumentValue>
Type argumentValueForTheNullableType =
GetFirstArgumentOfGenericType(
fi.FieldType);//fi.FieldType.GetGenericArguments()[0];
fi.SetValue(obj, info.GetValue(fi.Name,
argumentValueForTheNullableType));
}
else
{
fi.SetValue(obj, info.GetValue(fi.Name, fi.FieldType));
}
}
else
if (fi.FieldType.IsClass)
{
fi.SetValue(obj, info.GetValue(fi.Name, fi.FieldType));
}
}
return obj;
}
private Type GetFirstArgumentOfGenericType(Type type)
{
return type.GetGenericArguments()[0];
}
private bool IsNullableType(Type type)
{
if (type.IsGenericType)
return type.GetGenericTypeDefinition() == typeof(Nullable<>);
return false;
}
private bool IsKnownType(Type type)
{
return
type == typeof(string)
|| type.IsPrimitive
|| type.IsSerializable
;
}
#endregion
#region ISurrogateSelector Members
System.Runtime.Serialization.ISurrogateSelector _nextSelector;
public void ChainSelector(
System.Runtime.Serialization.ISurrogateSelector selector)
{
this._nextSelector = selector;
}
public System.Runtime.Serialization.ISurrogateSelector GetNextSelector()
{
return _nextSelector;
}
public System.Runtime.Serialization.ISerializationSurrogate GetSurrogate(
Type type, System.Runtime.Serialization.StreamingContext context,
out System.Runtime.Serialization.ISurrogateSelector selector)
{
if (IsKnownType(type))
{
selector = null;
return null;
}
else if (type.IsClass || type.IsValueType)
{
selector = this;
return this;
}
else
{
selector = null;
return null;
}
}
#endregion
}
序列化器使用代理来提供有关如何/从对象的特定实例序列化哪些信息。因此,用于克隆对象的最后一部分是这个,一个克隆方法扩展。
clone 方法是一个泛型方法,但由于 C# 3.0 现在知道如何进行类型推断,因此您可以像这样使用 int a = 10, a.Clone()
,而无需显式请求结果为 int,例如 a.Clone<int>()
。
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using CloningExtension;
namespace System
{
public static class DeepCloning
{
public static T Clone<T>(this T obj)
{
IFormatter formatter = new BinaryFormatter();
formatter.SurrogateSelector = new SurrogateSelector();
formatter.SurrogateSelector.ChainSelector(
new NonSerialiazableTypeSurrogateSelector());
var ms = new MemoryStream();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
}
现在我正在将最后几个部分联系起来。我使用了 BinaryFormatter
,但如果需要它提供其他格式化器(如我在开头指定的那些(OpenNX、CF、Soap)),使用 IoC 容器也可以实现。
正如您所看到的,链中的第一个选择器是 SurrogateSelector
。这用于防止每次遇到类时都调用 NonSerialiazableTypeSurrogateSelector
。它还允许您在到达 NonSerialiazableTypeSurrogateSelector
之前注册其他代理。
测试
没有什么是一成不变的,所以我已经对这个类在一些简单到中等复杂度的场景进行了测试。在使用它之前,请自行测试您的代码。
[TestMethod] public void Should_clone_primitive_types() { int aInt = 10; int bInt = aInt.Clone(); bInt.ShouldEqual(aInt); bInt.ShouldNotBeSameAs(aInt); string aString = "Another string"; string bString = aString.Clone<string>(); bString.ShouldEqual(aString); bString.ShouldNotBeSameAs(aString); float aFloat = 1.0f; float bFloat = aFloat.Clone(); bFloat.ShouldEqual(aFloat); bFloat.ShouldNotBeSameAs(aFloat); } [TestMethod] public void Should_clone_nullable_types() { decimal? aDecimal = 1.2m; decimal? bDecimal = aDecimal.Clone(); bDecimal.ShouldEqual(aDecimal); bDecimal.ShouldNotBeSameAs(aDecimal); } public class UsernameExample { public string Username { get; set; } } public class UsernameExampleHolder { public UsernameExample UsernameExample { get; set; } } [TestMethod] public void Should_clone_a_graph_of_objects() { var aUeh = new UsernameExampleHolder { UsernameExample = new UsernameExample { Username = "someUser" } }; var bUeh = aUeh.Clone(); bUeh.UsernameExample.Username.ShouldEqual(aUeh.UsernameExample.Username); bUeh.ShouldNotBeSameAs(aUeh); } [TestMethod] public void Should_serialiaze_a_simple_list_of_primitives() { var aList = new List<double>(); for (var step = 0.0; step < 100.0; step += 0.1) { aList.Add(step); } var bList = aList.Clone(); bList.ShouldNotBeSameAs(aList); for (var listIndex = 0; listIndex < aList.Count; listIndex++) { aList[listIndex].ShouldEqual(bList[listIndex]); } } [TestMethod] public void Should_serialize_a_dictionary_of_values() { var aDict = new Dictionary<string, List<char>>(); aDict.Add("a", new char[] { 'a', 'b', 'c' }.ToList()); aDict.Add("abra", new char[] { 'a', 'b', 'c' }.ToList()); aDict.Add("cadabra", new char[] { 'a', 'b', 'c' }.ToList()); var bDict = aDict.Clone(); bDict.ShouldNotBeSameAs(aDict); bDict.Keys.Count.ShouldBe(aDict.Keys.Count); }
注释
该代理使用反射。即使您使用委托方法添加动态调用,如果没有某种缓存,我认为您在性能方面也无法取得太大进展。它仍然是一个简单的方法,可以进行改进。
参考文献
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.surrogateselector.aspx
如果您觉得这篇文章有用,请点赞或点踩,并发表评论。这是让每个人都能学到新东西的最佳方式。