it-swarm.com.de

Der richtige Weg, um ICloneable zu implementieren

Was ist der richtige Weg, um ICloneable in einer Klassenhierarchie zu implementieren? Angenommen, ich habe eine abstrakte Klasse DrawingObject. Eine weitere abstrakte Klasse RectangularObject erbt von DrawingObject. Dann gibt es mehrere konkrete Klassen wie Shape, Text, Circle usw., die alle von RectangularObject erben. Ich möchte ICloneable auf DrawingObject implementieren und dann die Hierarchie nach unten ziehen, die verfügbaren Eigenschaften auf jeder Ebene kopieren und Clone des übergeordneten Objekts auf der nächsten Ebene aufrufen.

Das Problem ist jedoch, dass die ersten beiden Klassen abstrakt sind und ich ihre Objekte nicht in der Clone()-Methode erstellen kann. Daher muss ich das Eigenschaftskopierverfahren in jeder konkreten Klasse duplizieren. Oder gibt es einen besseren Weg?

41
dotNET

Sie können leicht einen oberflächlichen Klon mit objects geschützter Methode MemberwiseClone erstellen.

Beispiel:

   public abstract class AbstractCloneable : ICloneable
   {
      public object Clone()
      {
         return this.MemberwiseClone();
      }
   }

Wenn Sie keine tiefen Kopien benötigen, müssen Sie in den untergeordneten Klassen nichts tun.

Die MemberwiseClone-Methode erstellt eine flache Kopie, indem ein neues Objekt erstellt und anschließend die nicht statischen Felder des aktuellen Objekts in das neue Objekt kopiert werden. Wenn ein Feld ein Werttyp ist, wird eine bitweise Kopie des Felds durchgeführt. Wenn ein Feld ein Referenztyp ist, wird die Referenz kopiert, das referenzierte Objekt jedoch nicht. Daher beziehen sich das ursprüngliche Objekt und sein Klon auf dasselbe Objekt.

Wenn Sie mehr Intelligenz in der Klonlogik benötigen, können Sie eine virtuelle Methode zum Verarbeiten von Referenzen hinzufügen:

   public abstract class AbstractCloneable : ICloneable
   {
      public object Clone()
      {
         var clone = (AbstractCloneable) this.MemberwiseClone();
         HandleCloned(clone);
         return clone;
      }

      protected virtual HandleCloned(AbstractCloneable clone)
      {
         //Nothing particular in the base class, but maybe useful for children.
         //Not abstract so children may not implement this if they don't need to.
      }
   }


   public class ConcreteCloneable : AbstractCloneable
   {
       protected override HandleCloned(AbstractCloneable clone)
       {
           //Get whathever magic a base class could have implemented.
           base.HandleCloned(clone);

           //Clone is of the current type.
           ConcreteCloneable obj = (ConcreteCloneable) clone;

           //Here you have a superficial copy of "this". You can do whathever 
           //specific task you need to do.
           //e.g.:
           obj.SomeReferencedProperty = this.SomeReferencedProperty.Clone();
       }
   }
48
Johnny5

Weisen Sie Ihrer Basisklasse eine geschützte und überschreibbare Methode CreateClone() zu, mit der eine neue (leere) Instanz der aktuellen Klasse erstellt wird. Lassen Sie dann die Clone() -Methode der Basisklasse diese Methode aufrufen, um eine neue Instanz polymorph zu instanziieren, in die die Basisklasse dann ihre Feldwerte kopieren kann.

Abgeleitete nicht abstrakte Klassen können die Methode CreateClone() überschreiben, um die entsprechende Klasse zu instanziieren, und alle abgeleiteten Klassen, die neue Felder einführen, können Clone() überschreiben, um ihre zusätzlichen Feldwerte in die neue Instanz zu kopieren, nachdem die geerbte Version aufgerufen wurde von Clone().

public CloneableBase : ICloneable
{
    protected abstract CloneableBase CreateClone();

    public virtual object Clone()
    {
        CloneableBase clone = CreateClone();
        clone.MyFirstProperty = this.MyFirstProperty;
        return clone;
    }

    public int MyFirstProperty { get; set; }
}

public class CloneableChild : CloneableBase
{
    protected override CloneableBase CreateClone()
    {
        return new CloneableChild();
    }

    public override object Clone()
    {
        CloneableChild clone = (CloneableChild)base.Clone();
        clone.MySecondProperty = this.MySecondProperty;
        return clone;
    }

    public int MySecondProperty { get; set; }
}

Wenn Sie den ersten überschreibenden Schritt überspringen möchten (zumindest im Standardfall), können Sie auch eine Standardkonstruktorsignatur (z. B. ohne Parameter) annehmen und versuchen, eine Kloninstanz unter Verwendung dieser Konstruktorsignatur mit Reflektion zu instanziieren. Auf diese Weise müssen nur Klassen, deren Konstruktoren nicht mit der Standardsignatur übereinstimmen, CreateClone() überschreiben.

Eine sehr einfache Version dieser CreateClone() Standardimplementierung könnte folgendermaßen aussehen:

protected virtual CloneableBase CreateClone()
{
    return (CloneableBase)Activator.CreateInstance(GetType());
}
3
O. R. Mapper

Ich glaube, ich habe eine Verbesserung gegenüber der hervorragenden Antwort von @ johnny5. Definieren Sie einfach in allen Klassen Kopierkonstruktoren, und verwenden Sie in der Clone-Methode Reflection, um den Kopierkonstruktor zu suchen und auszuführen. Ich denke, dass dies etwas sauberer ist, da Sie keinen Stapel von Klone-Überschreibungen benötigen und MemberwiseClone () nicht benötigen, was in vielen Situationen einfach zu stumpf ist.

public abstract class AbstractCloneable : ICloneable
    {
        public int BaseValue { get; set; }
        protected AbstractCloneable()
        {
            BaseValue = 1;
        }
        protected AbstractCloneable(AbstractCloneable d)
        {
            BaseValue = d.BaseValue;
        }

        public object Clone()
        {
            var clone = ObjectSupport.CloneFromCopyConstructor(this);
            if(clone == null)throw new ApplicationException("Hey Dude, you didn't define a copy constructor");
            return clone;
        }

    }


    public class ConcreteCloneable : AbstractCloneable
    {
        public int DerivedValue { get; set; }
        public ConcreteCloneable()
        {
            DerivedValue = 2;
        }

        public ConcreteCloneable(ConcreteCloneable d)
            : base(d)
        {
            DerivedValue = d.DerivedValue;
        }
    }

    public class ObjectSupport
    {
        public static object CloneFromCopyConstructor(System.Object d)
        {
            if (d != null)
            {
                Type t = d.GetType();
                foreach (ConstructorInfo ci in t.GetConstructors())
                {
                    ParameterInfo[] pi = ci.GetParameters();
                    if (pi.Length == 1 && pi[0].ParameterType == t)
                    {
                        return ci.Invoke(new object[] { d });
                    }
                }
            }

            return null;
        }
    }

Zum Schluss möchte ich mich für ICloneable aussprechen. Wenn Sie diese Schnittstelle verwenden, werden Sie von der Stilpolizei geschlagen, weil die .NET Framework-Entwurfsrichtlinien sagen, dass sie nicht implementiert werden soll, weil, um die Richtlinien zu zitieren: "Wenn Sie ein Objekt verwenden, das einen Typ mit ICloneable implementiert, wissen Sie nie, was Sie tun bekommen werden. Dies macht das Interface unbrauchbar. " Das bedeutet, dass Sie nicht wissen, ob Sie eine tiefe oder flache Kopie erhalten. Nun, das ist einfach Sophistik. Bedeutet dies, dass Copy-Konstruktoren niemals verwendet werden sollten, weil "Sie nie wissen, was Sie bekommen werden?" Natürlich nicht. Wenn Sie nicht wissen, was Sie erhalten werden, ist dies einfach ein Problem mit dem Design der Klasse, nicht mit der Schnittstelle.

3
AQuirky

Meiner Meinung nach ist es am einfachsten, die binäre Serialisierung mit BinaryFormatter in MemoryStream anzuwenden.

Es gibt einen MSDN-Thread über tiefes Klonen in C # , in dem der oben beschriebene Ansatz vorgeschlagen wird.

Zumindest lassen Sie nur konkrete Klassen das Klonen handhaben, und abstrakte Klassen haben protected Copy-Konstruktoren. Außerdem möchten Sie eine Variable von DrawingObject nehmen und wie folgt klonen:

class Program
{
    static void Main(string[] args)
    {
        DrawingObject obj1=new Circle(Color.Black, 10);
        DrawingObject obj2=obj1.Clone();
    }
}

Sie könnten dies als Betrug betrachten, aber ich würde Erweiterungsmethoden und Reflexion verwenden:

public abstract class DrawingObject
{
    public abstract void Draw();
    public Color Color { get; set; }
    protected DrawingObject(DrawingObject other)
    {
        this.Color=other.Color;
    }
    protected DrawingObject(Color color) { this.Color=color; }
}

public abstract class RectangularObject : DrawingObject
{
    public int Width { get; set; }
    public int Height { get; set; }
    protected RectangularObject(RectangularObject other)
        : base(other)
    {
        Height=other.Height;
        Width=other.Width;
    }
    protected RectangularObject(Color color, int width, int height)
        : base(color)
    {
        this.Width=width;
        this.Height=height;
    }
}

public class Circle : RectangularObject, ICloneable
{
    public int Diameter { get; set; }
    public override void Draw()
    {
    }
    public Circle(Circle other)
        : base(other)
    {
        this.Diameter=other.Diameter;
    }
    public Circle(Color color, int diameter)
        : base(color, diameter, diameter)
    {
        Diameter=diameter;
    }

    #region ICloneable Members
    public Circle Clone() { return new Circle(this); }
    object ICloneable.Clone()
    {
        return Clone();
    }
    #endregion

}

public class Square : RectangularObject, ICloneable
{
    public int Side { get; set; }
    public override void Draw()
    {
    }
    public Square(Square other)
        : base(other)
    {
        this.Side=other.Side;
    }
    public Square(Color color, int side)
        : base(color, side, side)
    {
        this.Side=side;
    }

    #region ICloneable Members
    public Square Clone() { return new Square(this); }
    object ICloneable.Clone()
    {
        return Clone();
    }
    #endregion

}

public static class Factory
{
    public static T Clone<T>(this T other) where T : DrawingObject
    {
        Type t = other.GetType();
        ConstructorInfo ctor=t.GetConstructor(new Type[] { t });
        if (ctor!=null)
        {
            ctor.Invoke(new object[] { other });
        }
        return default(T);
    }
}

Bearbeiten 1

Wenn Sie mit der Geschwindigkeit vertraut sind (jedes Mal eine Reflexion durchführen), können Sie a) den Konstruktor in einem statischen Wörterbuch zwischenspeichern.

public static class Factory
{
    public static T Clone<T>(this T other) where T : DrawingObject
    {
        return Dynamic<T>.CopyCtor(other);
    }
}

public static class Dynamic<T> where T : DrawingObject
{
    static Dictionary<Type, Func<T, T>> cache = new Dictionary<Type,Func<T,T>>();

    public static T CopyCtor(T other)
    {
        Type t=other.GetType();
        if (!cache.ContainsKey(t))
        {
            var ctor=t.GetConstructor(new Type[] { t });
            cache.Add(t, (x) => ctor.Invoke(new object[] { x }) as T);
        }
        return cache[t](other);
    }
}
0
ja72