解惑,请教带字典的对象不能深拷贝的问题

最近我在琢磨深拷贝时候遇到了带字典的对象不能深拷贝的问题,请各位帮我解惑下,感激不尽。
这是我的深拷贝方法

public class DeepCopy
    {
        public static T DeepCopyByBin(T obj)
        {
            object retval;//序列化深拷贝
            using (MemoryStream ms = new MemoryStream()) //创建内存流 
            {
                XmlSerializer bf = new XmlSerializer(typeof(Box));  
                //序列化成流
                bf.Serialize(ms, obj);
                //反序列化当前实例到一个object 
                ms.Seek(0, SeekOrigin.Begin);
                retval = bf.Deserialize(ms);
                ms.Close();//关闭内存流            

            }
            return (T)retval;
        }
    }

这是我的对象和主方法

[Serializable]
   public  class Box
    {
        public double length;   // 长度
        public double breadth;  // 宽度
        public double height;   // 高度
        public DictionaryBox> Properties { get; set; }
        public Box DeepCopy()
        {
            Box ret = new Box();
            ret.length = this.length;
            ret.breadth = this.breadth;
            ret.height = this.height;
            return ret;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Box b1 = new Box();
            Box b3 = new Box();
            b1.length = 1;
            b1.breadth = 1;
            b1.height = 1;
            b1.Properties = new DictionaryBox>();
            b1.Properties.Add("xfd", b1);
            Box b2 = DeepCopy.DeepCopyByBin(b1);
            b2.breadth = 2;
            List<Box> list = new List<Box>();
            Console.WriteLine(b1.breadth);
            Console.WriteLine(b2.breadth);
            Console.ReadKey();
        }
    }

但是在调试的时候会报错,说字典不能序列化

报错文本:内部异常 1:
NotSupportedException: Cannot serialize member 序列化实现深拷贝.Box.Properties of type System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[序列化实现深拷贝.Box, 序列化实现深拷贝, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], because it implements IDictionary.

这里提供一个可以深拷贝字典的示例:

using Newtonsoft.Json;
namespace DemoApp1;

internal class Program
{
    private static void Main(string[] args)
    {
        var b1 = new Box();
        var b3 = new Box();
        b1.Length = 1;
        b1.Breadth = 1;
        b1.Height = 1;
        b1.Properties = new Dictionary<string, Box> { { "xfd", b3 } };
        var b2 = b1.Copy();
        b2.Breadth = 2;
        var list = new List<Box>();
        Console.WriteLine(b1.Breadth);
        Console.WriteLine(b2.Breadth);
        list.AddRange(new[] { b1, b2, b3 });
        Console.WriteLine(JsonConvert.SerializeObject(list));
        Console.ReadKey();
    }
}
public class Box
{
    public double Length;   // 长度
    public double Breadth;  // 宽度
    public double Height;   // 高度
    public Dictionary<string, Box> Properties { get; set; }
}

运行结果:

1
2
[{"Length":1.0,"Breadth":1.0,"Height":1.0,"Properties":{"xfd":{"Length":0.0,"Breadth":0.0,"Height":0.0,"Properties":null}}},{"Length":1.0,"Breadth":2.0,"Height":1.0,"Properties":{"xfd":{"Length":0.0,"Breadth":0.0,"Height":0.0,"Properties":null}}},{"Length":0.0,"Breadth":0.0,"Height":0.0,"Properties":null}]

ObjectExtensions.cs类和相关的静态扩展方法如下:

using DemoApp1.ArrayExtensions;
using System.Reflection;

namespace DemoApp1
{
    public static class ObjectExtensions
    {
        private static readonly MethodInfo CloneMethod = typeof(object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);

        public static bool IsPrimitive(this Type type)
        {
            if (type == typeof(string)) return true;
            return (type.IsValueType & type.IsPrimitive);
        }

        public static object Copy(this object originalObject)
        {
            return InternalCopy(originalObject, new Dictionary<object, object>(new ReferenceEqualityComparer()));
        }
        private static object InternalCopy(object originalObject, IDictionary<object, object> visited)
        {
            if (originalObject == null) return null;
            var typeToReflect = originalObject.GetType();
            if (IsPrimitive(typeToReflect)) return originalObject;
            if (visited.ContainsKey(originalObject)) return visited[originalObject];
            if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
            var cloneObject = CloneMethod.Invoke(originalObject, null);
            if (typeToReflect.IsArray)
            {
                var arrayType = typeToReflect.GetElementType();
                if (IsPrimitive(arrayType) == false)
                {
                    var clonedArray = (Array)cloneObject;
                    clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
                }

            }
            visited.Add(originalObject, cloneObject);
            CopyFields(originalObject, visited, cloneObject, typeToReflect);
            RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
            return cloneObject;
        }

        private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect)
        {
            if (typeToReflect.BaseType == null) return;
            RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
            CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
        }

        private static void CopyFields(object originalObject, IDictionary<object, object> visited, object cloneObject, IReflect typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null)
        {
            foreach (var fieldInfo in typeToReflect.GetFields(bindingFlags))
            {
                if (filter != null && filter(fieldInfo) == false) continue;
                if (IsPrimitive(fieldInfo.FieldType)) continue;
                var originalFieldValue = fieldInfo.GetValue(originalObject);
                var clonedFieldValue = InternalCopy(originalFieldValue, visited);
                fieldInfo.SetValue(cloneObject, clonedFieldValue);
            }
        }
        public static T Copy<T>(this T original)
        {
            return (T)Copy((object)original);
        }
    }

    public class ReferenceEqualityComparer : EqualityComparer<Object>
    {
        public override bool Equals(object x, object y)
        {
            return ReferenceEquals(x, y);
        }
        public override int GetHashCode(object obj)
        {
            return obj.GetHashCode();
        }
    }

    namespace ArrayExtensions
    {
        public static class ArrayExtensions
        {
            public static void ForEach(this Array array, Action<Array, int[]> action)
            {
                if (array.LongLength == 0) return;
                var walker = new ArrayTraverse(array);
                do action(array, walker.Position);
                while (walker.Step());
            }
        }

        internal class ArrayTraverse
        {
            public int[] Position;
            private readonly int[] _maxLengths;

            public ArrayTraverse(Array array)
            {
                _maxLengths = new int[array.Rank];
                for (var i = 0; i < array.Rank; ++i)
                {
                    _maxLengths[i] = array.GetLength(i) - 1;
                }
                Position = new int[array.Rank];
            }

            public bool Step()
            {
                for (var i = 0; i < Position.Length; ++i)
                {
                    if (Position[i] >= _maxLengths[i]) continue;
                    Position[i]++;
                    for (var j = 0; j < i; j++)
                    {
                        Position[j] = 0;
                    }
                    return true;
                }
                return false;
            }
        }
    }
}

查询到的一位博主写的"踩坑"记录,这个好像确实是不能。
字典的一些"踩坑"记一下:
1. 常见的集中利用反射、xml序列化和反序列化、利用二进制序列化和反序列化实现、DataContractSerializer实现全都无法实现字典的深拷贝。
要拷贝就自己动手写代码去低效率拷贝,所以谨慎吧。
2. 使用序列化存储变量,也无法存字典
[System.Serializable]
public Dictionary<a, b> Dic
这样可以被序列化成功不trace,但是load时加载会是空值。

因为 C# 没有提供 Dictionary(字典)的序列化(Serialize)功能 ,作为练习,你需要自己补上