제네릭 ICloneable<T>
이 존재하지 않는 특별한 이유 가 있습니까?
내가 무언가를 복제 할 때마다 그것을 캐스팅 할 필요가 없다면 훨씬 더 편안 할 것입니다.
제네릭 ICloneable<T>
이 존재하지 않는 특별한 이유 가 있습니까?
내가 무언가를 복제 할 때마다 그것을 캐스팅 할 필요가 없다면 훨씬 더 편안 할 것입니다.
답변:
ICloneable은 결과가 딥 카피인지 얕은 카피인지 지정하지 않기 때문에 현재 나쁜 API로 간주됩니다. 이것이 그들이이 인터페이스를 개선하지 않는 이유라고 생각합니다.
유형이 지정된 클로닝 확장 방법을 사용할 수는 있지만 확장 방법은 원래 방법보다 우선 순위가 낮으므로 다른 이름이 필요하다고 생각합니다.
List<T>
복제 방법이있는 경우 List<T>
원래 목록의 ID와 동일한 ID를 가진 항목 을 생성 할 것으로 예상되지만 하나의 목록에 수행 된 작업이 영향을 미치지 않도록 필요한 경우 내부 데이터 구조가 복제 될 것으로 예상합니다 다른쪽에 저장된 품목 의 신원 . 모호성은 어디에 있습니까? 복제와 관련하여 더 큰 문제는 "다이아몬드 문제"의 변형과 함께 발생합니다. CloneableFoo
[공개적으로 복제 Foo
할 CloneableDerivedFoo
수 없음 ]에서 상속 된 경우 다음 에서 파생 되어야합니다 .
identity
예를 들어 (목록 목록의 경우) 목록 자체 를 무엇으로 고려할 것이므로 기대를 완전히 이해한다고 말할 수는 없습니다 . 그러나이를 무시하면 전화 나 구현시 사람들이 기대할 수있는 유일한 아이디어는 아닙니다 Clone
. 다른 목록을 구현하는 도서관 저자가 귀하의 기대를 따르지 않으면 어떻게됩니까? API는 분명하고 모호하지 않아야합니다.
Clone
모든 파트를 호출하는 클래스를 만들면 해당 파트가 본인 또는 그 사람에 의해 구현되었는지 여부에 따라 예상대로 작동하지 않습니다. 깊은 복제를 좋아하는 사람. 패턴에 대한 요점은 유효하지만 API에 IMHO가 있다는 것은 명확하지 않습니다 ShallowCopy
. 요점을 강조하기 위해 호출 하거나 전혀 제공하지 않아야합니다.
안드레이의 답변뿐만 아니라 (나는 동의 +1) - 때 ICloneable
입니다 수행, 당신은 또한 공개 할 수있는 명시 적 구현을 선택할 수 있습니다 Clone()
반환 형식화 된 개체를 :
public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}
물론 일반 ICloneable<T>
상속 에는 두 번째 문제가 있습니다.
만약 내가 가지고 있다면:
public class Foo {}
public class Bar : Foo {}
그리고 구현 ICloneable<T>
한 다음 구현 ICloneable<Foo>
합니까? ICloneable<Bar>
? 많은 동일한 인터페이스를 빠르게 구현하기 시작합니다 ... 캐스트와 비교할 때 ... 정말 나쁩니 까?
인터페이스가 아닌 다른 인터페이스로 정확히 무엇 을 하시겠습니까? 인터페이스는 일반적으로 캐스트 (예 :이 클래스가 'IBar'를 지원함)하거나이를 사용하는 매개 변수 또는 설정자가있는 경우에만 유용합니다 (즉, 'IBar'를 사용함). ICloneable을 사용하여 전체 프레임 워크를 살펴보고 구현 이외의 다른 곳에서는 단일 사용을 찾지 못했습니다. 우리는 또한 '실제 세계'에서 구현 이외의 작업을 수행하는 데 실패했습니다 (우리가 액세스 할 수있는 ~ 60,000 개의 앱에서).
이제 '복제 가능한'객체를 구현하려는 패턴을 적용하고 싶다면 완전히 좋은 사용법입니다. 또한 "복제"가 자신에게 의미하는 바를 정확하게 결정할 수 있습니다 (즉, 깊거나 얕음). 그러나이 경우 우리 (BCL)가이를 정의 할 필요는 없습니다. 우리는 관련이없는 라이브러리간에 추상화로 유형이 지정된 인스턴스를 교환해야하는 경우에만 BCL에서 추상화를 정의합니다.
데이비드 킨 (BCL 팀)
ICloneable<out T>
상속 된 경우 매우 유용 할 수 있습니다 . 하나는 종종 "복제 가능한 것"을 필요로하지 않지만, 복제 가능한 것을 필요로 할 수도 있습니다 . 복제 가능한 객체가 구현 하는 경우 복제 가능한 파생물이 모두 공통 조상 을 공유 하지는 않지만 복제 가능한 객체 를 필요로하는 루틴 은 type의 매개 변수를 허용 할 수 있습니다 . ISelf<out T>
Self
T
T
ISelf<itsOwnType>
T
ICloneable<T>
T
ICloneable<T>
병렬 변경 가능 및 불변 클래스를 유지하기위한 더 넓은 프레임 워크가 더 도움이 될 수 있지만 이와 같은 것이 유용 할 수 있습니다. 즉, 필요 코드의 몇 가지 유형이 무엇인지 볼 Foo
이 들어 있지만, 어느 쪽도 그것을 돌연변이도 이제까지 변화는 사용할 수 없습니다 것으로 예상 것 없다 IReadableFoo
..., 동안
Foo
는 사용할 수있는 ImmutableFoo
while 코드를 사용할 수 있습니다 MutableFoo
. 모든 유형의 코드 IReadableFoo
는 변경 가능하거나 변경 불가능한 버전을 얻을 수 있어야합니다. 이러한 프레임 워크는 훌륭하지만 불행히도 일반적인 방식으로 설정하는 좋은 방법을 찾을 수 없습니다. 클래스에 대해 읽기 전용 래퍼를 만드는 일관된 방법이 있다면, ' ICloneable<T>
를 보유한 클래스의 불변 사본을 만드는 데 이러한 것을 사용할 수 있습니다 T
.
List<T>
, 복제 된 List<T>
원본이 원본 컬렉션의 모든 동일한 객체에 대한 포인터를 보유하는 새로운 컬렉션이 되도록하려면 ICloneable<T>
. 첫 번째는 것입니다 Enumerable.ToList()
확장 방법 : List<foo> clone = original.ToList();
두 번째는이다 List<T>
소요 생성자 IEnumerable<T>
: List<foo> clone = new List<foo>(original);
내가 확장 방법은 아마 생성자를 호출 생각하지만, 이들 모두는 당신이 요청하는 일을 할 것입니다. ;)
"왜"라는 질문은 불필요하다고 생각합니다. 매우 유용한 인터페이스 / 클래스 / 등이 많이 있지만 .NET Frameworku 기본 라이브러리의 일부는 아닙니다.
그러나 주로 스스로 할 수 있습니다.
public interface ICloneable<T> : ICloneable {
new T Clone();
}
public abstract class CloneableBase<T> : ICloneable<T> where T : CloneableBase<T> {
public abstract T Clone();
object ICloneable.Clone() { return this.Clone(); }
}
public abstract class CloneableExBase<T> : CloneableBase<T> where T : CloneableExBase<T> {
protected abstract T CreateClone();
protected abstract void FillClone( T clone );
public override T Clone() {
T clone = this.CreateClone();
if ( object.ReferenceEquals( clone, null ) ) { throw new NullReferenceException( "Clone was not created." ); }
return clone
}
}
public abstract class PersonBase<T> : CloneableExBase<T> where T : PersonBase<T> {
public string Name { get; set; }
protected override void FillClone( T clone ) {
clone.Name = this.Name;
}
}
public sealed class Person : PersonBase<Person> {
protected override Person CreateClone() { return new Person(); }
}
public abstract class EmployeeBase<T> : PersonBase<T> where T : EmployeeBase<T> {
public string Department { get; set; }
protected override void FillClone( T clone ) {
base.FillClone( clone );
clone.Department = this.Department;
}
}
public sealed class Employee : EmployeeBase<Employee> {
protected override Employee CreateClone() { return new Employee(); }
}
필요한 경우 인터페이스를 직접 작성하는 것이 매우 쉽습니다 .
public interface ICloneable<T> : ICloneable
where T : ICloneable<T>
{
new T Clone();
}
최근에 객체 복사가 끔찍한 이유 기사를 읽은 적이 있습니까? , 나는이 질문에 추가 clafirication이 필요하다고 생각합니다. 여기에있는 다른 답변은 좋은 조언을 제공하지만 여전히 답변이 완전하지 않습니다 ICloneable<T>
. 왜 안 됩니까?
용법
그래서 그것을 구현하는 클래스가 있습니다. 이전에는 원하는 방법이 ICloneable
있었지만 이제는 일반적으로 받아 들여야 ICloneable<T>
합니다. 편집해야합니다.
그런 다음 object 여부를 확인하는 메소드를 가질 수 있습니다 is ICloneable
. 지금 무엇? 당신은 할 수 없으며 is ICloneable<>
컴파일 유형의 객체 유형을 알지 못하므로 메소드를 일반으로 만들 수는 없습니다. 첫 번째 진짜 문제.
따라서 전자 ICloneable<T>
와 ICloneable
후자를 모두 구현해야합니다. , 따라서 구현은 두 가지 방법을 구현해야합니다 - object Clone()
와 T Clone()
. 아니요. 감사 IEnumerable
합니다.
이미 지적했듯이 상속의 복잡성도 있습니다. 공분산이이 문제를 해결하는 것처럼 보이지만 파생 된 형식 ICloneable<T>
은 자체 형식 을 구현해야 하지만 Clone()
기본 클래스 와 동일한 서명 (기본적으로 매개 변수)을 가진 메서드가 이미 있습니다. 새로운 클론 메소드 인터페이스를 명시 적으로 만드는 것은 의미가 없으며, 생성 할 때 추구했던 이점을 잃게됩니다 ICloneable<T>
. new
키워드를 추가하십시오 . 그러나 기본 클래스를 재정의해야한다는 것을 잊지 마십시오 ' Clone()
(구현은 모든 파생 클래스에 대해 균일하게 유지되어야합니다 (즉, 모든 클론 메소드에서 동일한 객체를 반환해야하므로 기본 클론 메소드는이어야 함 virtual
))! 그러나, 불행하게도, 당신은 할 수 없습니다 모두 override
와new
동일한 서명을 가진 메소드. 첫 번째 키워드를 선택하면를 추가 할 때 원하는 목표를 잃게됩니다 ICloneable<T>
. 두 번째 것을 선택하면 인터페이스 자체가 깨져서 동일한 작업을 수행하는 메서드가 다른 객체를 반환하게됩니다.
포인트
당신이 원하는 ICloneable<T>
편안하지만 편안 C #으로,가, 예를 들어 메서드와 속성을 외부 행동을 통일로 제한되어 있지만, (객체의 동작을하지 단일화 (일반 OOP에서) 그 의미는, 위해 설계 어떤 인터페이스를하지 않습니다 그들의 작업).
첫 번째 이유가 아직 확실하지 않은 경우, ICloneable<T>
복제 메소드에서 리턴 된 유형을 제한하기 위해 제한적으로 작동 할 수있는 오브젝트를 반대 할 수 있습니다. 그러나 불쾌한 프로그래머는 ICloneable<T>
T가 그것을 구현하는 유형이 아닌 곳에서 구현할 수 있습니다. 따라서 제한을 달성하려면 일반 매개 변수에 좋은 제약 조건을 추가 할 수 있습니다.
public interface ICloneable<T> : ICloneable where T : ICloneable<T>
확실히없는 매개 변수 보다 더 제한적으로 T 는 인터페이스를 구현하는 유형으로 where
제한 할 수 없습니다 ( 다른 유형 에서 파생 될 수 있음 ) 그것을 구현합니다).ICloneable<T>
당신은이 목적조차도 달성 할 수 없었습니다 (원본 ICloneable
도 이것에 실패하고 인터페이스가 실제로 구현 클래스의 동작을 제한 할 수는 없습니다).
보시다시피, 이것은 일반 인터페이스를 완전히 구현하기가 어렵고 실제로 불필요하고 쓸모 없게 만듭니다.
그러나 질문으로 돌아가서, 실제로 추구하는 것은 객체를 복제 할 때 위로를 얻는 것입니다. 이를 수행하는 두 가지 방법이 있습니다.
public class Base : ICloneable
{
public Base Clone()
{
return this.CloneImpl() as Base;
}
object ICloneable.Clone()
{
return this.CloneImpl();
}
protected virtual object CloneImpl()
{
return new Base();
}
}
public class Derived : Base
{
public new Derived Clone()
{
return this.CloneImpl() as Derived;
}
protected override object CloneImpl()
{
return new Derived();
}
}
이 솔루션은 사용자에게 편안함과 의도 된 동작을 모두 제공하지만 구현하기에는 너무 길다. 현재 유형을 리턴하는 "편안한"메소드를 원하지 않으면 그냥 사용하는 것이 훨씬 쉽다 public virtual object Clone()
.
그렇다면 "궁극적 인"솔루션을 보도록하겠습니다. C #에서 실제로 우리에게 편안함을주는 것은 무엇입니까?
public class Base : ICloneable
{
public virtual object Clone()
{
return new Base();
}
}
public class Derived : Base
{
public override object Clone()
{
return new Derived();
}
}
public static T Copy<T>(this T obj) where T : class, ICloneable
{
return obj.Clone() as T;
}
현재 Clone 메서드 와 충돌하지 않도록 Copy 라는 이름이 지정됩니다 (컴파일러는 확장 메서드보다 형식 자체 선언 된 메서드를 선호합니다). 제약은 속도가 (등을 확인 NULL 필요하지 않습니다)입니다.class
나는 이것이 왜하지 않는 이유를 명확히하기를 바랍니다 ICloneable<T>
. 그러나 전혀 구현하지 않는 것이 좋습니다 ICloneable
.
ICloneable
방법은 Clone 메서드의 복싱을 우회 할 수있는 값 유형에 대해서만 사용되며 , 값을 unboxed로 나타냅니다. 또한 구조를 자동으로 (얕게) 복제 할 수 있으므로 구현할 필요가 없습니다 (딥 카피를 구체적으로 지정하지 않는 한).
질문이 매우 오래되었지만 (이 답변을 작성한 후 5 년 :) 이미 답변되었지만이 기사가 질문에 잘 대답한다는 것을 알았습니다. 여기 에서 확인 하십시오.
편집하다:
다음은 질문에 대답하는 기사의 인용문입니다 (전체 기사를 읽으십시오. 다른 흥미로운 것들이 포함되어 있습니다).
인터넷상에서 2003 년 브래드 아브람 (Brad Abrams)의 블로그 게시물을 가리키고있다. 블로그 항목은 다음 주소에서 찾을 수 있습니다 . ICloneable 구현 . 오해의 소지가 있지만이 블로그 항목은 주로 얕은 / 깊은 혼동 때문에 ICloneable을 구현하지 않아야합니다. 복제 제안이 필요한 경우 복제 기법 또는 복제 방법을 정의하고 깊거나 얕은 사본인지 명확하게 문서화해야합니다. 적절한 패턴은 다음과 같습니다.
public <type> Copy();
큰 문제는 T를 동일한 클래스로 제한 할 수 없다는 것입니다. 예를 들어, 이렇게하지 못하는 이유는 다음과 같습니다.
interface IClonable<T>
{
T Clone();
}
class Dog : IClonable<JackRabbit>
{
//not what you would expect, but possible
JackRabbit Clone()
{
return new JackRabbit();
}
}
다음과 같은 매개 변수 제한이 필요합니다.
interfact IClonable<T> where T : implementing_type
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
제한 할 수있는 T
자신의 유형과 일치하는, 그의 구현을 강요하지 것이다 Clone()
원격으로는 복제 된시 객체를 닮은 반환 아무것도. 또한 인터페이스 공분산을 사용하는 경우 구현 ICloneable
된 클래스를 봉인하고 인터페이스에 자체 반환되는 속성이 ICloneable<out T>
포함 되도록 하는 것이 가장 좋습니다 Self
.
ICloneable<BaseType>
또는 ICloneable<ICloneable<BaseType>>
. BaseType
문제는이 있어야 protected
하는 구현 형태에 의해 호출 될 것이다 복제에 대한 방법을 ICloneable
. 이 디자인은 Container
, a CloneableContainer
, a FancyContainer
및 a 를 가질 수 있으며 , CloneableFancyContainer
후자는 복제 가능한 파생물 Container
을 필요로하거나 FancyContainer
(복제 가능한지 신경 쓰지 않는) 코드에서 사용할 수 있습니다.
FancyList
현명하게 복제 할 수 있는 유형이있을 수 있지만 파생물은 디스크 파일 (생성자에 지정된)에 자동으로 상태를 유지할 수 있습니다. 파생 된 형식은 상태를 변경 가능한 단일 톤 (파일)의 상태에 연결하기 때문에 복제 할 수 없었지만 대부분의 기능이 필요 FancyList
하지만 필요하지 않은 곳에서는 파생 된 형식의 사용을 배제해서는 안됩니다 그것을 복제합니다.
아주 좋은 질문입니다 ...하지만 스스로 만들 수도 있습니다.
interface ICloneable<T> : ICloneable
{
new T Clone ( );
}
Andrey는 나쁜 API로 간주되지만이 인터페이스에 대한 지원이 중단되는 소식을 듣지 못했습니다. 그리고 그것은 수많은 인터페이스를 망칠 것입니다 ... Clone 메소드는 얕은 복사를 수행해야합니다. 객체가 딥 카피도 제공하는 경우 오버로드 된 클론 (bool deep)을 사용할 수 있습니다.
편집 : 개체를 "복제"하는 데 사용하는 패턴은 생성자에서 프로토 타입을 전달합니다.
class C
{
public C ( C prototype )
{
...
}
}
이것은 잠재적 인 중복 코드 구현 상황을 제거합니다. ICloneable의 한계에 대해 이야기하는 BTW는 얕은 클론 또는 딥 클론, 또는 심지어 얕은 / 부분 딥 클론을 수행해야하는지 결정하는 것이 실제로 객체 자체에 달려 있지 않습니까? 대상이 의도 한대로 작동하는 한 우리가 정말로 관심을 가져야합니까? 경우에 따라 우수한 복제 구현에는 얕은 복제와 깊은 복제가 모두 포함될 수 있습니다.