[C#] 복사 생성자와 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 ()을 구현 해야한다는 훌륭한 주장이 있습니다.

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

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

(권장되는 공용 인터페이스는 생성자 기반이 아닌 Clone () 인터페이스입니다.)

다른 답변에서 명시적인 깊거나 얕은 논쟁에 사로 잡히지 마십시오. 현실 세계에서는 거의 항상 중간에있는 것입니다. 어느 쪽이든 호출자의 관심사가되어서는 안됩니다.

Clone () 계약은 단순히 “첫 번째 계약을 변경할 때 변경되지 않습니다”입니다. 얼마나 많은 그래프를 복사해야하는지 또는이를 발생시키기 위해 무한 재귀를 피하는 방법은 호출자와 관련이 없습니다.


답변

ICloneable을 구현 하는 것은 딥 또는 얕은 복사본인지 여부가 지정되지 않았기 때문에 권장 되지 않으므로 생성자를 선택하거나 직접 구현할 것입니다. 정말 분명하게 만들기 위해 DeepCopy ()라고 부를 수도 있습니다!


답변

복사 생성자 및 추상 클래스에 문제가 발생합니다. 다음을 수행하고 싶다고 가정 해보십시오.

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

위의 작업을 수행 한 직후 인터페이스를 사용했거나 DeepCopy () / ICloneable.Clone () 구현에 만족하기를 바라기 시작합니다.


답변

ICloneable의 문제는 의도와 일관성입니다. 딥 카피인지 얕은 카피인지는 분명하지 않습니다. 그 때문에 아마도 한 가지 또는 다른 방식으로 만 사용되지 않을 것입니다.

나는 그 문제에 대해 더 명확한 공개 복사 생성자를 찾지 못했습니다.

즉, 나는 당신을 위해 작동하고 의도를 전달하는 방법 시스템을 소개 할 것입니다 (다소 자체 문서화)