ВНИМАНИЕ:
Този код трябва да се използва с голяма доза внимание. Използвайте на свой собствен риск. Този пример се предоставя такъв, какъвто е и без никаква гаранция.
Има още един начин за извършване на дълбоко клониране на обектна графика. Важно е да имате предвид следното, когато обмисляте използването на тази проба:
Недостатъци:
- Всички препратки към външни класове също ще бъдат клонирани, освен ако тези препратки не са предоставени на метода Clone(object, ...).
- Няма да се изпълняват конструктори върху клонирани обекти, те се възпроизвеждат ТОЧНО такива, каквито са.
- Няма да се изпълняват конструктори ISerializable или конструктори за сериализация.
- Няма начин да се промени поведението на този метод върху определен тип.
- ЩЕ клонира всичко, Stream, App Domain, Form, каквото и да е, и те вероятно ще разбият приложението ви по ужасяващи начини.
- Може да се повреди, докато използването на метода за сериализация е много по-вероятно да продължи да работи.
- Реализацията по-долу използва рекурсия и може лесно да причини препълване на стека, ако вашата обектна графика е твърде дълбока.
Така че защо искате да го използвате?
Плюсове:
- Той прави пълно дълбоко копие на всички данни за екземпляри, без да се изисква кодиране в обекта.
- Той запазва всички препратки към графа на обект (дори кръгови) в реконструирания обект.
- Той се изпълнява повече от 20 пъти по-натоварено от двоичния форматиращ инструмент с по-малко потребление на памет.
- Не изисква нищо, никакви атрибути, внедрени интерфейси, публични свойства, нищо.
Използване на код:
Просто го извиквате с обект:
Class1 copy = Clone(myClass1);
Или да кажем, че имате дъщерен обект и сте абонирани за неговите събития... Сега искате да клонирате този дъщерен обект. Като предоставите списък с обекти, които да не се клонират, можете да запазите част от отвара от обектната графика:
Class1 copy = Clone(myClass1, this);
Внедряване:
Сега нека първо махнем лесните неща от пътя... Ето входната точка:
public static T Clone<T>(T input, params object[] stableReferences)
{
Dictionary<object, object> graph = new Dictionary<object, object>(new ReferenceComparer());
foreach (object o in stableReferences)
graph.Add(o, o);
return InternalClone(input, graph);
}
Сега това е достатъчно просто, той просто изгражда речникова карта за обектите по време на клонирането и я попълва с всеки обект, който не трябва да бъде клониран. Ще забележите, че инструментът за сравнение, предоставен на речника, е ReferenceComparer, нека да разгледаме какво прави:
class ReferenceComparer : IEqualityComparer<object>
{
bool IEqualityComparer<object>.Equals(object x, object y)
{ return Object.ReferenceEquals(x, y); }
int IEqualityComparer<object>.GetHashCode(object obj)
{ return RuntimeHelpers.GetHashCode(obj); }
}
Това беше достатъчно лесно, просто компаратор, който принуждава използването на получаване на хеш и референтно равенство на System.Object... сега идва тежката работа:
private static T InternalClone<T>(T input, Dictionary<object, object> graph)
{
if (input == null || input is string || input.GetType().IsPrimitive)
return input;
Type inputType = input.GetType();
object exists;
if (graph.TryGetValue(input, out exists))
return (T)exists;
if (input is Array)
{
Array arItems = (Array)((Array)(object)input).Clone();
graph.Add(input, arItems);
for (long ix = 0; ix < arItems.LongLength; ix++)
arItems.SetValue(InternalClone(arItems.GetValue(ix), graph), ix);
return (T)(object)arItems;
}
else if (input is Delegate)
{
Delegate original = (Delegate)(object)input;
Delegate result = null;
foreach (Delegate fn in original.GetInvocationList())
{
Delegate fnNew;
if (graph.TryGetValue(fn, out exists))
fnNew = (Delegate)exists;
else
{
fnNew = Delegate.CreateDelegate(input.GetType(), InternalClone(original.Target, graph), original.Method, true);
graph.Add(fn, fnNew);
}
result = Delegate.Combine(result, fnNew);
}
graph.Add(input, result);
return (T)(object)result;
}
else
{
Object output = FormatterServices.GetUninitializedObject(inputType);
if (!inputType.IsValueType)
graph.Add(input, output);
MemberInfo[] fields = inputType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
object[] values = FormatterServices.GetObjectData(input, fields);
for (int i = 0; i < values.Length; i++)
values[i] = InternalClone(values[i], graph);
FormatterServices.PopulateObjectMembers(output, fields, values);
return (T)output;
}
}
Веднага ще забележите специалния случай за копиране на масив и делегат. Всеки има свои собствени причини, първо Array няма „членове“, които могат да бъдат клонирани, така че трябва да се справите с това и да зависи от плиткия Clone() член и след това да клонирате всеки елемент. Що се отнася до делегата, той може да работи без специалния случай; това обаче ще бъде много по-безопасно, тъй като не дублира неща като RuntimeMethodHandle и други подобни. Ако възнамерявате да включите други неща във вашата йерархия от основната среда за изпълнение (като System.Type), предлагам да се справите с тях изрично по подобен начин.
Последният и най-често срещан случай е просто да се използват приблизително същите процедури, които се използват от BinaryFormatter. Те ни позволяват да извадим всички полета на екземпляра (публични или частни) от оригиналния обект, да ги клонираме и да ги поставим в празен обект. Хубавото тук е, че GetUninitializedObject връща нов екземпляр, който не е изпълнявал ctor, което може да причини проблеми и да забави производителността.
Дали горното работи или не ще зависи до голяма степен от вашата конкретна обектна графика и данните в нея. Ако контролирате обектите в графиката и знаете, че те не препращат към глупави неща като нишка, тогава горният код трябва да работи много добре.
Тестване:
Ето какво написах, за да тествам първоначално това:
class Test
{
public Test(string name, params Test[] children)
{
Print = (Action<StringBuilder>)Delegate.Combine(
new Action<StringBuilder>(delegate(StringBuilder sb) { sb.AppendLine(this.Name); }),
new Action<StringBuilder>(delegate(StringBuilder sb) { sb.AppendLine(this.Name); })
);
Name = name;
Children = children;
}
public string Name;
public Test[] Children;
public Action<StringBuilder> Print;
}
static void Main(string[] args)
{
Dictionary<string, Test> data2, data = new Dictionary<string, Test>(StringComparer.OrdinalIgnoreCase);
Test a, b, c;
data.Add("a", a = new Test("a", new Test("a.a")));
a.Children[0].Children = new Test[] { a };
data.Add("b", b = new Test("b", a));
data.Add("c", c = new Test("c"));
data2 = Clone(data);
Assert.IsFalse(Object.ReferenceEquals(data, data2));
//basic contents test & comparer
Assert.IsTrue(data2.ContainsKey("a"));
Assert.IsTrue(data2.ContainsKey("A"));
Assert.IsTrue(data2.ContainsKey("B"));
//nodes are different between data and data2
Assert.IsFalse(Object.ReferenceEquals(data["a"], data2["a"]));
Assert.IsFalse(Object.ReferenceEquals(data["a"].Children[0], data2["a"].Children[0]));
Assert.IsFalse(Object.ReferenceEquals(data["B"], data2["B"]));
Assert.IsFalse(Object.ReferenceEquals(data["B"].Children[0], data2["B"].Children[0]));
Assert.IsFalse(Object.ReferenceEquals(data["B"].Children[0], data2["A"]));
//graph intra-references still in tact?
Assert.IsTrue(Object.ReferenceEquals(data["B"].Children[0], data["A"]));
Assert.IsTrue(Object.ReferenceEquals(data2["B"].Children[0], data2["A"]));
Assert.IsTrue(Object.ReferenceEquals(data["A"].Children[0].Children[0], data["A"]));
Assert.IsTrue(Object.ReferenceEquals(data2["A"].Children[0].Children[0], data2["A"]));
data2["A"].Name = "anew";
StringBuilder sb = new StringBuilder();
data2["A"].Print(sb);
Assert.AreEqual("anew\r\nanew\r\n", sb.ToString());
}
Последна бележка:
Честно казано, това беше забавно упражнение по това време. Като цяло е страхотно да имате дълбоко клониране върху модел на данни. Днешната реалност е, че повечето модели на данни се генерират, което обезценява полезността на хакерството по-горе с генерирана рутина за дълбоко клониране. Силно препоръчвам да генерирате своя модел на данни и неговата способност да извършва дълбоки клонинги, вместо да използвате кода по-горе.
person
csharptest.net
schedule
15.10.2009
Clone
методи приемат аргумент (който не използват)? Правилният подпис еobject ICloneable.Clone()
- без аргументи. - person Pavel Minaev   schedule 15.10.2009