Мне нужно реализовать конструкторы глубокого копирования C # с наследованием. Какие модели можно выбрать?

Краткий ответ: Нет.

DynamoDB - это хранилище ключей:. Он очень хорош в быстром извлечении / сохранении элементов, потому что он выполняет несколько компромиссов. Это ограничение, с которым вы должны справиться.

Nonethess, в зависимости от вашей реальной модели, может быть хорошей идеей использовать это поле в качестве hash_key или использовать range_key

Если это невозможно, я советую вам де-нормализовать ваши данные. В настоящее время у вас есть что-то вроде:

UserTable

  • hash_key: user_id
  • e-mail
  • ...

Чтобы обеспечить однозначность, добавьте новую таблицу с этой схемой:

EmailUser

  • hash_key: e-mail
  • user_id

Чтобы убедиться, что электронное письмо уникально, перед отправкой еще раз отправьте GetItem на EmailUser.

Этот тип де-нормализации довольно распространен с базами данных без SQL.

13
задан JasonMArcher 8 July 2015 в 04:31
поделиться

8 ответов

Типичный подход - использовать шаблон «конструктор копирования» а-ля C ++:

 class Base : ICloneable
 { 
     int x;

     protected Base(Base other)
     {
         x = other.x;
     }

     public virtual object Clone()
     {
         return new Base(this);
     }
 }

 class Derived : Base
 { 
     int y;

     protected Derived(Derived other)
          : Base(other)
     {
         y = other.y;
     }

     public override object Clone()
     {
         return new Derived(this);
     }
 }

Другой подход - использовать Object.MemberwiseClone в реализации Clone - это гарантирует, что результат всегда будет правильного типа, и позволит расширять переопределения:

 class Base : ICloneable
 { 
     List<int> xs;

     public virtual object Clone()
     {
         Base result = this.MemberwiseClone();

         // xs points to same List object here, but we want
         // a new List object with copy of data
         result.xs = new List<int>(xs);

         return result;
     }
 }

 class Derived : Base
 { 
     List<int> ys;

     public override object Clone()
     {
         // Cast is legal, because MemberwiseClone() will use the
         // actual type of the object to instantiate the copy.
         Derived result = (Derived)base.Clone();

         // ys points to same List object here, but we want
         // a new List object with copy of data
         result.ys = new List<int>(ys);

         return result;
     }
 }

Оба подхода требуют, чтобы все классы в иерархии следовали шаблону. Какой из них использовать, - вопрос предпочтений.

Если у вас есть какой-то случайный класс, реализующий ICloneable без каких-либо гарантий реализации (кроме соблюдения документированной семантики ICloneable ), нет возможности продлить его.

30
ответ дан 1 December 2019 в 17:55
поделиться

попробуйте трюк с сериализацией:

public object Clone(object toClone)
{
    BinaryFormatter bf = new BinaryFormatter();
    MemoryStream ms= new MemoryStream();
    bf.Serialize(ms, toClone);
    ms.Flush();
    ms.Position = 0;
    return bf.Deserialize(ms);
}
7
ответ дан 1 December 2019 в 17:55
поделиться

Вместо этого вам следует использовать метод MemberwiseClone :

public class ParentObj : ICloneable
{
    protected int myA;
    public virtual Object Clone()
    {
        ParentObj newObj = this.MemberwiseClone() as ParentObj;
        newObj.myA = this.MyA; // not required, as value type (int) is automatically already duplicated.
        return newObj;
    }
}

public class ChildObj : ParentObj
{
    protected int myB;
    public override Object Clone()
        {
             ChildObj newObj = base.Clone() as ChildObj;
             newObj.myB = this.MyB; // not required, as value type (int) is automatically already duplicated

             return newObj;
        }
}
-1
ответ дан 1 December 2019 в 17:55
поделиться
  1. Вы можете использовать отражение для зацикливания всех переменных и их копирования. (Медленно), если это слишком медленно для вашего программного обеспечения, вы можете использовать DynamicMethod и сгенерировать il.
  2. сериализовать объект и снова десериализовать его.
0
ответ дан 1 December 2019 в 17:55
поделиться

Лучше всего сериализовать ваш объект, а затем вернуть десериализованную копию. Он подберет все о вашем объекте, кроме тех, которые помечены как несериализуемые, и упростит наследование сериализации.

[Serializable]
public class ParentObj: ICloneable
{
    private int myA;
    [NonSerialized]
    private object somethingInternal;

    public virtual object Clone()
    {
        MemoryStream ms = new MemoryStream();
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(ms, this);
        object clone = formatter.Deserialize(ms);
        return clone;
    }
}

[Serializable]
public class ChildObj: ParentObj
{
    private int myB;

    // No need to override clone, as it will still serialize the current object, including the new myB field
}

Это не самая эффективная вещь, но и альтернатива - размышление. Преимущество этого варианта в том, что он наследует.

2
ответ дан 1 December 2019 в 17:55
поделиться

ПРЕДУПРЕЖДЕНИЕ:

Этот код следует использовать с большой осторожностью. Используйте на свой риск. Этот пример предоставляется как есть и без каких-либо гарантий.


Существует еще один способ выполнить глубокое клонирование графа объекта. При рассмотрении использования этого примера важно помнить следующее:

Минусы:

  1. Любые ссылки на внешние классы также будут клонированы, если эти ссылки не предоставлены методу Clone (object, ...).
  2. Никакие конструкторы не будут выполняться для клонированных объектов, они воспроизводятся ТОЧНО, как они есть.
  3. Никакие конструкторы ISerializable или сериализации не будут выполняться.
  4. Невозможно изменить поведение этого метода для определенного типа.
  5. 127] Он БУДЕТ клонировать все, Stream, AppDomain, Form, что угодно, и те , скорее всего, приведут к ужасным поломкам вашего приложения. Предоставляя список объектов для , а не клона, вы можете сохранить некоторую часть графа объектов:

    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 get hash и ссылочное равенство ... теперь идет тяжелая работа:

    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;
        }
    }
    

    Вы сразу заметите особый случай для массива и копии делегата. У каждого есть свои причины, первый массив не имеет «членов», которые можно клонировать, поэтому вы должны справиться с этим и зависеть от мелкого члена 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());
    }
    

    Заключительное примечание:

    Честно говоря, в то время это было забавное упражнение. Как правило, глубокое клонирование модели данных - это хорошо. Сегодняшняя реальность такова, что создается большинство моделей данных, которые сводят на нет полезность описанного выше хакерства с помощью сгенерированной процедуры глубокого клонирования. Я настоятельно рекомендую сгенерировать вашу модель данных и все такое.

7
ответ дан 1 December 2019 в 17:55
поделиться

Я не думаю, что вы правильно реализуете ICloneable; Для этого требуется метод Clone () без параметров. Я бы порекомендовал примерно следующее:

public class ParentObj : ICloneable
{
    public virtual Object Clone()
    {
        var obj = new ParentObj();

        CopyObject(this, obj);
    }

    protected virtual CopyObject(ParentObj source, ParentObj dest)
    {
        dest.myA = source.myA;
    }
}

public class ChildObj : ParentObj
{
    public override Object Clone()
    {
        var obj = new ChildObj();
        CopyObject(this, obj);
    }

    public override CopyObject(ChildObj source, ParentObj dest)
    {
        base.CopyObject(source, dest)
        dest.myB = source.myB;
    }
}

Обратите внимание, что CopyObject () - это, по сути, Object.MemberwiseClone (), предположительно, вы будете делать больше, чем просто копировать значения, вы также будете клонировать любые члены, являющиеся классами.

0
ответ дан 1 December 2019 в 17:55
поделиться

Попробуйте использовать следующее [используйте ключевое слово "new"]

public class Parent
{
  private int _X;
  public int X{ set{_X=value;} get{return _X;}}
  public Parent copy()
  {
     return new Parent{X=this.X};
  }
}
public class Child:Parent
{
  private int _Y;
  public int Y{ set{_Y=value;} get{return _Y;}}
  public new Child copy()
  {
     return new Child{X=this.X,Y=this.Y};
  }
}
0
ответ дан 1 December 2019 в 17:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: