Ошибка в словаре глубокой копии, как решить? - PullRequest
0 голосов
/ 17 марта 2019

У меня есть следующий код, который создает исключение KeyNotFoundException для dict2[a] в цикле for.После некоторых копаний я обнаружил, что это связано с методом копирования, который я использовал для создания dict2, который я взял из этой темы, второй ответ .

Проблема в том, что поле entriesв словарь копируется точно.Каждая запись содержит поле, которое содержит HashCode ключа.Он также держит сам ключ.Когда ключевой объект копируется, он получает новый HashCode, но поле в записи копируется с HashCode старого ключа.Я добавил скриншот, чтобы прояснить это:

Снимок экрана с полем entries

Есть какие-нибудь подсказки, как обойти эту проблему (в идеале, без жесткого кодирования исключения для словарей)?

    [TestMethod]
    public void SimpleTest()
    {
        var dict = new Dictionary<MyClass, MyClass>();
        dict.Add(new MyClass(3), new MyClass(5));
        dict.Add(new MyClass(77), new MyClass(99));

        var dict2 = dict.Copy();

        var a2 = dict2.Keys.ToList();

        for (int i = 0; i < a2.Count; i++)
        {
            var a = a2[i];
            if (dict2[a] as MyClass != null)
                a.DoSomething();
        }
    }

    public class MyClass
    {
        public int SomeProperty { get; set; }

        public MyClass(int prop)
        {
            SomeProperty = prop;
        }

        public void DoSomething()
        { }

        public int UID
        {
            get
            {
                return this.GetHashCode();
            }
        }

        public override string ToString()
        {
            return string.Format("Prop: {0} with UID: {1}", SomeProperty, UID);
        }
    }

Код для копирования такой:

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)
            {
                Array 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)
        {
            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, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null)
    {
        foreach (FieldInfo 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);
            // TODO: Perhaps fix next if by something more robust?
            if (fieldInfo.FieldType == typeof(Guid))
                clonedFieldValue = Guid.Empty;
            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)
    {
        if (obj == null) return 0;
        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;
            ArrayTraverse walker = new ArrayTraverse(array);
            do action(array, walker.Position);
            while (walker.Step());
        }
    }

    internal class ArrayTraverse
    {
        public int[] Position;
        private int[] maxLengths;

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

        public bool Step()
        {
            for (int i = 0; i < Position.Length; ++i)
            {
                if (Position[i] < maxLengths[i])
                {
                    Position[i]++;
                    for (int j = 0; j < i; j++)
                    {
                        Position[j] = 0;
                    }
                    return true;
                }
            }
            return false;
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...