C #에서 클래스에 (심층) 복사 기능을 추가하는 데 선호되는 방법은 무엇입니까? 복사 생성자를 구현해야합니까, 아니면 메서드 에서 파생되어 ICloneable구현해야 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"이지만, 이것이 문서화되어 있고 팀 내에서 잘 이해되는지 확인해야합니다. 개인적으로 프레임 워크 지침을 고수합니다.)
IDeepCloneable<T>당신이 즉, 자신의 구현을 만들 경우 당신은 무엇 T를 알고 있기 때문에 ... 존재SomeClass : IDeepCloneable<SomeClass> { ... }
MyFunc(IDeepClonable data)특정 유형뿐만 아니라 모든 복제 가능 항목에서 작동 할 수 있습니다. 또는 복제 가능 컬렉션이있는 경우. IEnumerable<IDeepClonable> lotsOfCloneables그런 다음 동시에 많은 개체를 복제 할 수 있습니다. 그래도 그런 종류의 것이 필요하지 않다면 비 일반적인 것을 버리십시오.
C #에서 클래스에 (심층) 복사 기능을 추가하는 데 선호되는 방법은 무엇입니까? 복사 생성자를 구현해야합니까, 아니면 ICloneable에서 파생하여 Clone () 메서드를 구현해야합니까?
문제 ICloneable는 다른 사람들이 언급했듯이 딥 또는 얕은 사본인지 여부를 지정하지 않아 실제로 사용할 수 없으며 실제로 거의 사용되지 않는다는 것입니다. 또한 object많은 캐스팅이 필요하기 때문에 고통스러운을 반환합니다 . (그리고 질문에서 클래스를 구체적으로 언급했지만에서 구현 하려면 권투 ICloneable가 struct필요합니다.)
복사 생성자도 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 ()라고 부를 수도 있습니다!
IDeepCloneable<T>인터페이스를 정의합니다 DeepClone().
복사 생성자 및 추상 클래스에 문제가 발생합니다. 다음을 수행하고 싶다고 가정 해보십시오.
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 () 구현에 만족하기를 바라기 시작합니다.
복사하려는 개체가 직렬화 가능하면 직렬화 및 역 직렬화하여 복제 할 수 있습니다. 그러면 각 클래스에 대해 복사 생성자를 작성할 필요가 없습니다.
지금은 코드에 액세스 할 수 없지만 다음과 같습니다.
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
}
개발자로 정의해야하는 문제의 클래스의 복사 의미에 따라 다릅니다. 선택한 방법은 일반적으로 클래스의 의도 된 사용 사례를 기반으로합니다. 두 방법을 모두 구현하는 것이 합리적 일 수 있습니다. 그러나 둘 다 비슷한 단점을 공유합니다. 구현하는 복사 방법이 정확히 명확하지 않습니다. 이는 수업 문서에 명확하게 명시되어야합니다.
나를 위해 :
// 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();
대신에:
// 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();
더 명확한 의도의 진술로 보였습니다.
복제 가능한 개체에 대한 표준 패턴이 있어야한다고 생각하지만 패턴이 정확히 무엇이어야하는지 잘 모르겠습니다. 복제와 관련하여 세 가지 유형의 클래스가있는 것 같습니다.
내가 말할 수있는 한, 기존 객체와 동일한 클래스의 새 객체를 얻는 유일한 방법 (적어도 .net 2.0에서는)은 MemberwiseClone을 사용하는 것입니다. 좋은 패턴은 항상 현재 유형을 반환하는 "new"/ "Shadows"함수 Clone을 갖는 것 같습니다. 정의는 항상 MemberwiseClone을 호출 한 다음 보호 된 가상 서브 루틴 CleanupClone (originalObject)을 호출하는 것입니다. CleanupCode 루틴은 base.Cleanupcode를 호출하여 기본 유형의 복제 요구를 처리 한 다음 자체 정리를 추가해야합니다. 복제 루틴이 원래 객체를 사용해야하는 경우 유형 변환해야하지만 그렇지 않으면 MemberwiseClone 호출에서만 유형 변환이 수행됩니다.
불행히도, 유형 (2)가 아닌 (1) 유형의 가장 낮은 수준의 클래스는 하위 유형이 복제에 대한 명시적인 지원이 필요하지 않다고 가정하도록 코딩해야합니다. 나는 그것에 대해 어떤 방법도 보지 못한다.
그래도 정의 된 패턴을 갖는 것이없는 것보다 낫다고 생각합니다.
덧붙여서, 자신의 기본 유형이 iCloneable을 지원한다는 것을 알고 있지만 사용하는 함수의 이름을 모르는 경우 기본 유형의 iCloneable.Clone 함수를 참조 할 수있는 방법이 있습니까?
흥미로운 답변과 토론을 모두 읽은 경우에도 속성을 정확히 복사하는 방법 (모든 속성을 명시 적으로 복사하는 방법)에 대해 자문 해 볼 수 있습니다. 아니면 더 우아한 방법이 있습니까? 이것이 남은 질문이라면 다음을 살펴보십시오 (StackOverflow에서).
일반 확장 메서드를 사용하여 타사 클래스의 속성을 "심층적으로"복제하려면 어떻게해야합니까?
CreateCopy()모든 속성을 포함하는 객체의 "깊은"복사본을 만드는 확장 메서드를 구현하는 방법을 설명합니다 (속성별로 속성을 수동으로 복사 할 필요없이).