65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (5投票s)

2009 年 1 月 9 日

CPOL

4分钟阅读

viewsIcon

83354

downloadIcon

550

一种在 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;
    }

最后两个函数的作用是:

  1. 确定类型是否是 Nullable<> 的扩展

    默认情况下,当我们编写 int? 时,编译器会在后台为我们生成一个 Nullable<int>。

  2. 获取 Nullable<> 实例中包含的内容的类型信息,并从格式化器传递给我们的 serializationInfo 对象加载其值。

SetObjectData 的实现中可以看到,我像以前一样使用反射来检索存储在 SerializationInfo 对象中的值。这样,我们应该也能够反序列化任何没有 SerializableAttribute 的对象。

现在我们需要一种方法来为所有类型的对象使用我们的代理。因此,我们需要一种方法来选择我们代理选择器的所有类型的实现,而无需显式指定它。我们的做法是通过 ISurrogateSelectorISurrogateSelector 接口公开以下方法

方法

名称 描述
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

如果您觉得这篇文章有用,请点赞或点踩,并发表评论。这是让每个人都能学到新东西的最佳方式。

© . All rights reserved.