Форум программистов, компьютерный форум, киберфорум
Psilon
Войти
Регистрация
Восстановить пароль
Рейтинг: 4.83. Голосов: 6.

Создание полной копии объекта при помощи рефлексии

Запись от Psilon размещена 04.08.2013 в 17:36

В общем, решил выложить результат небольших исследований на тему сабжа.

Долгое время пытался понять, почему Microsoft забросили интерфейс ICloneable, почему не сделали обобщенный интерфейс ICloneable<T>, который был бы C#-аналогом конструктора копирования в C++. В интернете я находил такой ответ:
Цитата:
ICloneable is considered a bad API now, since it does not specify whether the result is a deep or a shallow copy. I think this is why they do not improve this interface.
примерный перевод:
Цитата:
Считается, что интерфейс ICloneable обладает плохим API, т.к. он не дает определить, насколько глубоко производится копирование. Именно поэтому они (Microsoft) не развивают дальше этот интерфейс.
Так или иначе, мне хотелось определить метод расширения, Который позволит полностью копировать объекты. И, как мне кажется, своего я добился. Весь код ниже. В чем он заключается: если объект можно сериализовать, то это простейший способ - мы им пользуемся. Если же нет, то пытаемся с помощью рефлексии достать все нужные нам параметры.
Cloner
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
 
    public static class Cloner
    {
        const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        public static T Clone<T>(this T obj)
        {
            if (typeof (T).IsValueType)
                return obj;
            if (!obj.IsSerializable())
                return obj.CloneReflect();
            var cloneable = obj as ICloneable;
            if (cloneable != null)
                return (T) cloneable.Clone();
            var b = new BinaryFormatter();
            byte[] buffer;
            using (var stream = new MemoryStream())
            {
                b.Serialize(stream, obj);
                buffer = stream.GetBuffer();
            }
 
            T result;
            using (var stream = new MemoryStream(buffer))
                result = (T) b.Deserialize(stream);
            return result;
        }
 
        public static bool IsSerializable<T>(this T obj)
        {
            return typeof (T).Attributes.HasFlag(TypeAttributes.Serializable) || obj is ISerializable;
        }
 
        private static T CloneReflect<T>(this T obj)
        {
            try
            {
                Type type = obj.GetType();
                if (type.IsValueType || obj is string)
                    return obj;
 
 
                T copy;
 
                try
                {
                    copy = Activator.CreateInstance<T>();
                }
                catch
                {
                    var constr = type.GetConstructors(Flags).First();
                    ParameterInfo[] parameterInfos = constr.GetParameters();
                    var parameters =
                        parameterInfos.Select(x => x.ParameterType)
                                      .Select(t => t.IsValueType ? Activator.CreateInstance(t) : null)
                                      .ToArray();
                    copy = (T)constr.Invoke(parameters);
                }
 
 
                foreach (var fieldInfo in type.GetFields(Flags))
                {
                    var value = fieldInfo.GetValue(obj).Clone();
                    fieldInfo.SetValue(copy, value);
                }
                foreach (var propertyInfo in type.GetProperties(Flags))
                {
                    var value = propertyInfo.GetValue(obj, null).Clone();
                    propertyInfo.SetValue(copy, value, null);
                }
 
                return copy;
            }
            catch (Exception ex)
            {
                throw new NotSupportedException("Не удается получить данные о типе", ex);
            }
        }    
}


Пример использования

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using System;
 
 
internal class My : IEquatable<My>
{
    public readonly string A, B, C;
    private readonly string _d;
    private string E { get; set; }
    public int[] Ints { get; set; }
 
    public My(string a, string b, string c, string d, string e)
    {
        A = a;
        B = b;
        C = c;
        _d = d;
        E = e;
    }
 
    public bool Equals(My other)
    {
        return A == other.A && B == other.B && C == other.C && _d == other._d && E == other.E;
    }
 
    public override string ToString()
    {
        return string.Format("{0} {1} {2}", A, B, C);
    }
}
 
public class Program
{
    private static void Main()
    {
        int[] ints = {1, 2, 3};
        My my = new My("ABC", "hello", "world", "wowow", "aba") {Ints = ints};
        Console.WriteLine(my + "\n\n");
 
        My another = my.Clone();
        Console.WriteLine(another);
 
        if (ReferenceEquals(my, another))
            Console.WriteLine("Same object");
        else
            Console.WriteLine("Another object");
        Console.WriteLine(my.Equals(another));
 
        Console.ReadKey();
    }
}


Надеюсь, это будет кому-нибудь полезно и/или интересно.
Размещено в Без категории
Просмотров 1980 Комментарии 0
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2020, vBulletin Solutions, Inc.