Programming

복사 생성자와 Clone ()

procodes 2020. 8. 7. 21:57
반응형

복사 생성자와 Clone ()


C #에서 클래스에 (심층) 복사 기능을 추가하는 데 선호되는 방법은 무엇입니까? 복사 생성자를 구현해야합니까, 아니면 메서드 에서 파생되어 ICloneable구현해야 Clone()합니까?

비고 : 부적합하다고 생각했기 때문에 괄호 안에 "깊이"라고 적었습니다. 분명히 다른 사람들이 동의하지 않기 때문에 복사 생성자 / 연산자 / 함수가 구현하는 복사 변형을 명확히해야하는지 물었 습니다 .


에서 파생해서는 안됩니다 ICloneable.

그 이유는 Microsoft가 .net 프레임 워크를 설계 할 때 Clone()메서드 ICloneable가 딥 또는 얕은 복제 인지 여부를 지정하지 않았기 때문에 호출자가 호출이 개체를 딥 복제할지 얕게 복제할지 알 수 없기 때문에 인터페이스가 의미 상 손상됩니다.

대신 (및 ) 메서드를 사용하여 자신의 IDeepCloneable(및 IShallowCloneable) 인터페이스를 정의해야 합니다.DeepClone()ShallowClone()

두 개의 인터페이스를 정의 할 수 있습니다. 하나는 강력한 유형의 복제를 지원하는 일반 매개 변수가있는 인터페이스이고 다른 하나는 서로 다른 유형의 복제 가능한 객체 컬렉션으로 작업 할 때 약한 유형의 복제 기능을 유지하지 않는 인터페이스입니다.

public interface IDeepCloneable
{
    object DeepClone();
}
public interface IDeepCloneable<T> : IDeepCloneable
{
    T DeepClone();
}

그런 다음 다음과 같이 구현합니다.

public class SampleClass : IDeepCloneable<SampleClass>
{
    public SampleClass DeepClone()
    {
        // Deep clone your object
        return ...;
    }
    object IDeepCloneable.DeepClone()   
    {
        return this.DeepClone();
    }
}

일반적으로 나는 의도를 매우 명확하게 유지하는 복사 생성자와 반대로 설명 된 인터페이스를 사용하는 것을 선호합니다. 복사 생성자는 아마도 딥 클론으로 간주 될 수 있지만 IDeepClonable 인터페이스를 사용하는 것만 큼 명확한 의도는 아닙니다.

이는 .net 프레임 워크 디자인 지침Brad Abrams의 블로그 에서 논의됩니다.

(프레임 워크 / 라이브러리가 아닌 애플리케이션을 작성하는 경우 팀 외부의 누구도 코드를 호출하지 않을 것임을 확신 할 수 있으며, 그다지 중요하지 않으며 의미 론적 의미를 할당 할 수 있습니다. .net ICloneable 인터페이스에 대한 "deepclone"이지만, 이것이 문서화되어 있고 팀 내에서 잘 이해되는지 확인해야합니다. 개인적으로 프레임 워크 지침을 고수합니다.)


C #에서 클래스에 (심층) 복사 기능을 추가하는 데 선호되는 방법은 무엇입니까? 복사 생성자를 구현해야합니까, 아니면 ICloneable에서 파생하여 Clone () 메서드를 구현해야합니까?

문제 ICloneable는 다른 사람들이 언급했듯이 딥 또는 얕은 사본인지 여부를 지정하지 않아 실제로 사용할 수 없으며 실제로 거의 사용되지 않는다는 것입니다. 또한 object많은 캐스팅이 필요하기 때문에 고통스러운을 반환합니다 . (그리고 질문에서 클래스를 구체적으로 언급했지만를 구현 하려면 권투 ICloneablestruct필요합니다.)

복사 생성자는 ICloneable의 문제 중 하나를 겪습니다. 복사 생성자가 딥 또는 얕은 복사를 수행하는지 여부는 명확하지 않습니다.

Account clonedAccount = new Account(currentAccount); // Deep or shallow?

DeepClone () 메서드를 만드는 것이 가장 좋습니다. 이렇게하면 의도가 완벽하게 명확 해집니다.

이로 인해 정적 메서드인지 인스턴스 메서드인지에 대한 질문이 제기됩니다.

Account clonedAccount = currentAccount.DeepClone();  // instance method

또는

Account clonedAccount = Account.DeepClone(currentAccount); // static method

복제가 개체가 수행하는 작업이 아닌 개체에 수행되는 작업처럼 보이기 때문에 가끔 정적 버전을 약간 선호합니다. 두 경우 모두 상속 계층의 일부인 개체를 복제 할 때 처리해야 할 문제가 있으며 이러한 문제가 어떻게 해결되는지 궁극적으로 설계를 주도 할 수 있습니다.

class CheckingAccount : Account
{
    CheckAuthorizationScheme checkAuthorizationScheme;

    public override Account DeepClone()
    {
        CheckingAccount clone = new CheckingAccount();
        DeepCloneFields(clone);
        return clone;
    }

    protected override void DeepCloneFields(Account clone)
    {
        base.DeepCloneFields(clone);

        ((CheckingAccount)clone).checkAuthorizationScheme = this.checkAuthorizationScheme.DeepClone();
    }
}

복제 메서드를 readonly사용하면 대신 생성자를 사용했다면 가능했을 수있는 필드 를 만들 수 없기 때문에 주로 복제 메서드보다 복사 생성자를 사용하는 것이 좋습니다 .

다형성 복제가 필요한 경우 복사 생성자를 호출하여 구현하는 기본 클래스에 abstract또는 virtual Clone()메서드를 추가 할 수 있습니다 .

두 종류 이상의 복사 (예 : deep / shallow)가 필요한 경우 복사 생성자에서 매개 변수를 사용하여 지정할 수 있지만, 경험상 일반적으로 딥 복사와 얕은 복사의 혼합이 필요합니다.

전의:

public class BaseType {
   readonly int mBaseField;

   public BaseType(BaseType pSource) =>
      mBaseField = pSource.mBaseField;

   public virtual BaseType Clone() =>
      new BaseType(this);
}

public class SubType : BaseType {
   readonly int mSubField;

   public SubType(SubType pSource)
   : base(pSource) =>
      mSubField = pSource.mSubField;

   public override BaseType Clone() =>
      new SubType(this);
}

보호 된 복사 생성자를 사용하여 clone ()을 구현 해야한다는 훌륭한 주장이 있습니다.

보호 된 (비 공용) 복사 생성자를 제공하고 복제 메서드에서 호출하는 것이 더 좋습니다. 이를 통해 객체 생성 작업을 클래스 자체의 인스턴스에 위임 할 수 있으므로 확장 성을 제공하고 보호 된 복사 생성자를 사용하여 객체를 안전하게 생성 할 수 있습니다.

따라서 이것은 "대"질문이 아닙니다. 복사 생성자와 복제 인터페이스가 모두 필요할 수 있습니다.

(Although the recommended public interface is the Clone() interface rather than Constructor-based.)

Don't get caught-up in the explicit deep or shallow argument in the other answers. In the real world it is almost always something in-between - and either way, should not be the caller's concern.

The Clone() contract is simply "won't change when I change the first one". How much of the graph you have to copy, or how you avoid infinite recursion to make that happen shouldn't concern the caller.


Implementing ICloneable's not recommended due to the fact that it's not specified whether it's a deep or shallow copy, so I'd go for the constructor, or just implement something yourself. Maybe call it DeepCopy() to make it really obvious!


You'll run into problems with copy constructors and abstract classes. Imagine you want to do the following:

abstract class A
{
    public A()
    {
    }

    public A(A ToCopy)
    {
        X = ToCopy.X;
    }
    public int X;
}

class B : A
{
    public B()
    {
    }

    public B(B ToCopy) : base(ToCopy)
    {
        Y = ToCopy.Y;
    }
    public int Y;
}

class C : A
{
    public C()
    {
    }

    public C(C ToCopy)
        : base(ToCopy)
    {
        Z = ToCopy.Z;
    }
    public int Z;
}

class Program
{
    static void Main(string[] args)
    {
        List<A> list = new List<A>();

        B b = new B();
        b.X = 1;
        b.Y = 2;
        list.Add(b);

        C c = new C();
        c.X = 3;
        c.Z = 4;
        list.Add(c);

        List<A> cloneList = new List<A>();

        //Won't work
        //foreach (A a in list)
        //    cloneList.Add(new A(a)); //Not this time batman!

        //Works, but is nasty for anything less contrived than this example.
        foreach (A a in list)
        {
            if(a is B)
                cloneList.Add(new B((B)a));
            if (a is C)
                cloneList.Add(new C((C)a));
        }
    }
}

Right after doing the above, you start wishing you'd either used an interface, or settled for a DeepCopy()/ICloneable.Clone() implementation.


The problem with ICloneable is both intent and consistency. It's never clear whether it is a deep or shallow copy. Because of that, it's probably never used in only one manner or another.

I don't find a public copy constructor to be any clearer on that matter.

That said, I would introduce a method system that works for you and relays intent (a'la somewhat self documenting)


If the object you are trying to copy is Serializable you can clone it by serializing it and deserializing it. Then you don't need to write a copy constructor for each class.

I don't have access to the code right now but it is something like this

public object DeepCopy(object source)
{
   // Copy with Binary Serialization if the object supports it
   // If not try copying with XML Serialization
   // If not try copying with Data contract Serailizer, etc
}

It is dependent on copy semantics of the class in question, which you should define yourself as the developer. Chosen method is usually based on intended use cases of the class. Maybe it will make a sense to implement both methods. But both share similar disadvantage - it is not exactly clear which copying method they implement. This should be clearly stated in documentation for your class.

For me having:

// myobj is some transparent proxy object
var state = new ObjectState(myobj.State);

// do something

myobject = GetInstance();
var newState = new ObjectState(myobject.State);

if (!newState.Equals(state))
    throw new Exception();

instead of:

// myobj is some transparent proxy object
var state = myobj.State.Clone();

// do something

myobject = GetInstance();
var newState = myobject.State.Clone();

if (!newState.Equals(state))
    throw new Exception();

looked as clearer statement of intent.


I think there should be a standard pattern for cloneable objects, though I'm not sure what exactly the pattern should be. With regard to cloning, it would seem there are three types of classes:

  1. Those that explicitly support for deep cloning
  2. Those that where memberwise cloning will work as deep cloning, but which neither have nor need explicit support.
  3. Those which cannot be usefully deep cloned, and where memberwise cloning will yield bad results.

So far as I can tell, the only way (at least in .net 2.0) to get a new object of the same class as an existing object is to use MemberwiseClone. A nice pattern would seem to be to have a "new"/"Shadows" function Clone which always returns the present type, whose definition is always to call MemberwiseClone and then call a protected virtual subroutine CleanupClone(originalObject). The CleanupCode routine should call base.Cleanupcode to handle the base type's cloning needs and then add its own cleanup. If the cloning routine has to use the original object, it would have to be typecast, but otherwise the only typecasting would be on the MemberwiseClone call.

Unfortunately, the lowest level of class that was of type (1) above rather than type (2) would have to be coded to assume that its lower types would not need any explicit support for cloning. I don't really see any way around that.

Still, I think having a defined pattern would be better than nothing.

Incidentally, if one knows that one's base type supports iCloneable, but does not know the name of the function it uses, is there any way to reference the iCloneable.Clone function of one's base type?


If you read through all the interesting answers and discussions, you might still ask yourself how exactly you copy the properties - all of them explicitly, or is there a more elegant way to do it? If that is your remaining question, take a look at this (at StackOverflow):

How can I “deeply” clone the properties of 3rd party classes using a generic extension method?

It describes how to implement an extension method CreateCopy() which creates a "deep" copy of the object including all properties (without having to copy property by property manually).

참고URL : https://stackoverflow.com/questions/3345389/copy-constructor-versus-clone

반응형