Dlaczego nie ma ICloneable <T>?

222

Czy istnieje jakiś szczególny powód, dla którego ICloneable<T>nie istnieje rodzajowy ?

Byłoby o wiele wygodniej, gdybym nie musiał go rzucać za każdym razem, gdy coś klonuję.

Bluenuance
źródło
6
Nie! z całym szacunkiem dla „powodów”, zgadzam się z tobą, powinni byli go wdrożyć!
Shimmy Weitzhandler,
Byłoby miło, gdyby Microsoft to zdefiniował (problem z własnymi interfejsami polega na tym, że interfejsy w dwóch różnych zestawach będą niekompatybilne, nawet jeśli są semantycznie identyczne). Gdybym zaprojektował interfejs, miałby on trzech członków, Clone, Self i CloneIfMutable, z których każdy zwróciłby T (ostatni członek zwróciłby odpowiednio Clone lub Self). Członek Self umożliwiłby zaakceptowanie ICloneable (Foo) jako parametru, a następnie użycie go jako Foo, bez potrzeby typowania.
supercat
Umożliwiłoby to prawidłową hierarchię klas klonowania, w której dziedziczne klasy ujawniają chronioną metodę „klonowania” i mają zapieczętowane pochodne, które ujawniają metodę publiczną. Na przykład można mieć Pile, CloneablePile: Pile, EnhancedPile: Pile i CloneableEnhancedPile: EnhancedPile, z których żaden nie zostałby uszkodzony, gdyby został sklonowany (nawet jeśli nie wszyscy ujawniają publiczną metodę klonowania), a ponadto jeśli jest sklonowany, ale nie ujawnia żadnej metody klonowania). Procedura, która akceptuje ICloneable (stosu), może zaakceptować CloneablePile lub CloneableEnhancedPile ...
supercat 12.01.11
... mimo że CloneableEnhancedPile nie dziedziczy po CloneablePile. Zauważ, że jeśli EnhancedPile odziedziczył po CloneablePile, AdditionalEnhancedPile musiałby ujawnić publiczną metodę klonowania i mógłby zostać przekazany do kodu, który spodziewałby się go sklonować, naruszając zasadę substytucyjności Liskowa. Ponieważ CloneableEnhancedPile zaimplementuje ICloneable (Of EnhancedPile) i przez domniemanie ICloneable (Of Pile), może zostać przekazany do rutyny oczekującej klonowalnej pochodnej Pile.
supercat

Odpowiedzi:

111

ICloneable jest obecnie uważany za zły interfejs API, ponieważ nie określa, czy wynikiem jest głęboka, czy płytka kopia. Myślę, że właśnie dlatego nie poprawiają tego interfejsu.

Prawdopodobnie możesz wykonać typową metodę rozszerzania klonowania, ale myślę, że wymagałaby ona innej nazwy, ponieważ metody rozszerzenia mają mniejszy priorytet niż oryginalne.

Andrey Shchekin
źródło
8
Nie zgadzam się, źle lub źle, jest to bardzo przydatne w przypadku PŁYNNEGO klonowania, aw takich przypadkach naprawdę potrzebny jest Klon <T>, aby zapisać niepotrzebne boksowanie i rozpakowywanie.
Shimmy Weitzhandler
2
@AndreyShchekin: Co jest niejasnego w klonowaniu głębokim vs płytkim? Gdybym List<T>miał metodę klonowania, spodziewałbym się, że przyniesie ona pozycję, List<T>której elementy mają takie same tożsamości jak te z oryginalnej listy, ale oczekiwałbym, że wszelkie wewnętrzne struktury danych zostaną w razie potrzeby zduplikowane, aby zapewnić, że nic zrobione na jednej liście nie wpłynie na że tożsamość przedmiotów przechowywanych w drugiej. Gdzie jest dwuznaczność? Większy problem z klonowaniem wyposażony w odmianie „problem diament”: jeśli CloneableFoodziedziczy z [nie publicznie Cloneable] Foo, powinny CloneableDerivedFoopochodzić z ...
Supercat
1
@ supercat: Nie mogę powiedzieć, że w pełni rozumiem twoje oczekiwania, ponieważ co weźmiesz pod uwagę identityna samej liście, na przykład (w przypadku listy list)? Jednak ignorując to, twoje oczekiwania nie są jedynym możliwym pomysłem, jaki ludzie mogą mieć, dzwoniąc lub wdrażając Clone. Co jeśli autorzy bibliotek wdrażający inną listę nie spełnią twoich oczekiwań? Interfejs API powinien być trywialnie jednoznaczny, a nie prawdopodobnie jednoznaczny.
Andrey Shchekin
2
@supercat Więc mówisz o płytkiej kopii. Jednak w głębokiej kopii nie ma nic zasadniczo złego, na przykład jeśli chcesz wykonać odwracalną transakcję na obiektach w pamięci. Twoje oczekiwania oparte są na twoim przypadku użycia, inne osoby mogą mieć inne oczekiwania. Jeśli umieszczą twoje obiekty w swoich obiektach (lub odwrotnie), wyniki będą nieoczekiwane.
Andrey Shchekin,
5
@ supercat nie bierzesz pod uwagę, że jest częścią frameworka .NET, a nie częścią twojej aplikacji. więc jeśli utworzysz klasę, która nie wykonuje głębokiego klonowania, ktoś inny Clonestworzy klasę, która wykonuje głębokie klonowanie i wywoła wszystkie jego części, nie będzie działać przewidywalnie - w zależności od tego, czy ta część została zaimplementowana przez ciebie, czy przez tę osobę kto lubi głębokie klonowanie. twoje zdanie na temat wzorców jest poprawne, ale posiadanie IMHO w API nie jest wystarczająco jasne - albo należy je wywołać, ShallowCopyaby podkreślić punkt, albo wcale.
Andrey Shchekin
141

Oprócz odpowiedzi Andreya (z którą się zgadzam, +1) - kiedy ICloneable to zrobisz, możesz także wybrać jawną implementację, aby publiczny Clone()zwrot był typowanym obiektem:

public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}

Oczywiście istnieje druga kwestia z ICloneable<T>dziedziczeniem rodzajowym .

Jeżeli mam:

public class Foo {}
public class Bar : Foo {}

I wdrożyłem ICloneable<T>, a następnie czy wdrożę ICloneable<Foo>? ICloneable<Bar>? Szybko zaczynasz wdrażać wiele identycznych interfejsów ... Porównaj z obsadą ... i czy to naprawdę takie złe?

Marc Gravell
źródło
10
+1 Zawsze myślałem, że problem kowariancji był prawdziwym powodem
Tamas Czinege
Jest z tym problem: jawna implementacja interfejsu musi być prywatna , co spowodowałoby problemy, gdyby Foo w pewnym momencie musiało zostać przesłane do ICloneable ... Czy coś mi tu brakuje?
Joel in Gö
3
Mój błąd: okazuje się, że pomimo zdefiniowania jako prywatny metoda zostanie wywołana, jeśli Foo zostanie przekazany do IClonable (CLR przez C # 2nd Ed, s.319). Wygląda na dziwną decyzję projektową, ale tak jest. Więc Foo.Clone () podaje pierwszą metodę, a ((ICloneable) Foo) .Clone () podaje drugą metodę.
Joel in Gö
W rzeczywistości jawne implementacje interfejsu są, ściśle mówiąc, częścią publicznego API. Dzięki temu można je wywoływać publicznie przez casting.
Marc Gravell
8
Proponowane rozwiązanie na początku wydaje się świetne ... dopóki nie zorientujesz się, że w hierarchii klas chcesz, aby „public Foo Clone ()” był wirtualny i zastąpiony w klasach pochodnych, aby mogli zwrócić swój własny typ (i sklonować własne pola kierunek). Niestety, nie można zmienić typu zwrotu w przypadku przesłonięcia (wspomniany problem kowariancji). Powracasz więc z powrotem do pierwotnego problemu - jawna implementacja tak naprawdę niewiele kosztuje (oprócz zwracania podstawowego typu hierarchii zamiast „obiektu”) i wracasz do rzutowania większości Wyniki wywołania Clone (). Fuj!
Ken Beckett,
18

Muszę zapytać, co dokładnie zrobiłbyś z interfejsem innym niż wdrożenie? Interfejsy są zwykle użyteczne tylko wtedy, gdy się na nie rzucisz (tj. Czy ta klasa obsługuje „IBar”), lub mają parametry lub ustawiające parametry, które je przyjmują (tzn. Biorę „IBar”). Dzięki ICloneable - przeszliśmy przez cały Framework i nie udało nam się znaleźć żadnego użycia w innym miejscu, które byłoby czymś innym niż jego implementacja. Nie udało nam się również znaleźć żadnego zastosowania w „prawdziwym świecie”, które również robi coś innego niż wdrożenie (w ~ 60 000 aplikacji, do których mamy dostęp).

Teraz, jeśli chcesz wymusić wzorzec, który chcesz wdrożyć w swoich „klonowalnych” obiektach, jest to całkiem dobre zastosowanie - i śmiało. Możesz także zdecydować, co dokładnie oznacza dla ciebie „klonowanie” (tj. Głębokie lub płytkie). Jednak w takim przypadku nie musimy go (BCL) definiować. Abstrakcje definiujemy w BCL tylko wtedy, gdy zachodzi potrzeba wymiany instancji typowanych jako abstrakcja między niepowiązanymi bibliotekami.

David Kean (zespół BCL)

David Kean
źródło
1
Niespecyficzna funkcja ICloneable nie jest zbyt użyteczna, ale ICloneable<out T>może być całkiem przydatna, jeśli zostanie odziedziczona ISelf<out T>za pomocą jednej metody Selftypu T. Często nie potrzeba „czegoś, co można klonować”, ale równie dobrze może potrzebować czegoś, co można Tklonować. Jeśli implementuje się klonowalny obiekt ISelf<itsOwnType>, procedura, która wymaga Tklonowalnego, może zaakceptować parametr typu ICloneable<T>, nawet jeśli nie wszystkie klonowalne pochodne tego samego Tprzodka.
supercat
Dzięki. Jest to podobne do sytuacji obsady, o której wspomniałem powyżej (tj. „Czy ta klasa obsługuje„ IBar ”). Niestety wymyśliliśmy tylko bardzo ograniczone i izolowane scenariusze, w których można by wykorzystać fakt, że T można klonować. Czy masz na myśli sytuacje?
David Kean
Jedną rzeczą, która czasami jest niezręczna w .net, jest zarządzanie zmiennymi kolekcjami, gdy zawartość kolekcji ma być postrzegana jako wartość (tj. Chcę później poznać zestaw elementów, które są teraz w kolekcji). Przydałoby się coś podobnego ICloneable<T>, chociaż szersza struktura utrzymywania równoległych klas mutable i niezmiennych może być bardziej pomocna. Innymi słowy, kod, który musi zobaczyć, co Foozawiera jakiś rodzaj, ale nie będzie go mutować ani oczekiwać, że nigdy się nie zmieni, mógłby użyć IReadableFoo, podczas gdy ...
supercat
... kod, który chce przechowywać zawartość, Foomoże użyć ImmutableFookodu while, który chce nim manipulować, może użyć MutableFoo. Podany kod dowolnego typu IReadableFoopowinien być w stanie uzyskać wersję zmienną lub niezmienną. Taki framework byłby fajny, ale niestety nie mogę znaleźć żadnego fajnego sposobu na skonfigurowanie rzeczy w ogólny sposób. Gdyby istniał spójny sposób na utworzenie opakowania tylko do odczytu dla klasy, można by tego użyć w połączeniu z ICloneable<T>niezmienną kopią klasy, która przechowuje T„.
supercat
@ superuper Jeśli chcesz sklonować a List<T>, tak, że sklonowana List<T>jest nowa kolekcja zawierająca wskaźniki do wszystkich tych samych obiektów w oryginalnej kolekcji, istnieją dwa proste sposoby, aby to zrobić bez ICloneable<T>. Pierwsza to Enumerable.ToList()metoda rozszerzenia: List<foo> clone = original.ToList();druga to List<T>konstruktor, który przyjmuje IEnumerable<T>: List<foo> clone = new List<foo>(original);Podejrzewam, że metoda rozszerzenia prawdopodobnie po prostu wywołuje konstruktor, ale oba z nich wykonają to, o co prosisz. ;)
CptRobby
12

Myślę, że pytanie „dlaczego” jest niepotrzebne. Istnieje wiele interfejsów / klas / etc ... co jest bardzo przydatne, ale nie jest częścią podstawowej biblioteki Frameworku .NET.

Ale głównie możesz to zrobić sam.

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(); }
}
TcKs
źródło
Każda wykonalna metoda klonowania dla dziedzicznej klasy musi wykorzystywać Object.MemberwiseClone jako punkt początkowy (lub użyć refleksji), ponieważ w przeciwnym razie nie ma gwarancji, że klon będzie tego samego typu, co obiekt oryginalny. Mam całkiem niezły wzór, jeśli jesteś zainteresowany.
supercat
@ superupat, dlaczego więc nie udzielić odpowiedzi?
nawfal
„Istnieje wiele interfejsów / klas / etc ... co jest bardzo przydatne, ale nie jest częścią podstawowej biblioteki .NET Frameworku.” Czy możesz podać nam przykłady?
Krythic,
1
@ Krythic tj .: zaawansowane struktury danych, takie jak b-drzewo, czerwono-czarne drzewo, bufor koła. W '09 nie było krotek, ogólnych słabych referencji, zbieżnych kolekcji ...
TcKs
10

W razie potrzeby napisanie interfejsu jest bardzo proste :

public interface ICloneable<T> : ICloneable
        where T : ICloneable<T>
{
    new T Clone();
}
Mauricio Scheffer
źródło
Dołączyłem
2
A jak dokładnie ten interfejs ma działać? Aby wynik operacji klonowania był możliwy do rzutowania na typ T, klonowany obiekt musi pochodzić od T. Nie ma możliwości skonfigurowania takiego ograniczenia typu. Realnie rzecz biorąc, jedyną rzeczą, jaką widzę w wyniku zwrotu iCloneable, jest typ iCloneable.
supercat
@ superupat: tak, dokładnie to zwraca Clone (): T, który implementuje ICloneable <T>. MbUnit używa tego interfejsu od lat , więc tak, działa. Jeśli chodzi o implementację, zajrzyj do źródła MbUnit.
Mauricio Scheffer
2
@ Mauricio Scheffer: Widzę, co się dzieje. Żaden typ nie implementuje iCloneable (z T). Zamiast tego każdy typ implementuje iCloneable (sam w sobie). Wydaje mi się, że to zadziałałoby, chociaż wystarczy przesunąć rzut. Szkoda, że ​​nie ma możliwości zadeklarowania pola jako mającego ograniczenie dziedziczenia i interfejsu; pomocna byłaby metoda klonowania zwracająca obiekt typu półklonowalnego (klonowanie ujawnione tylko jako metoda chroniona) typu podstawowego, ale z ograniczeniem typu klonowalnego. Pozwoliłoby to obiektowi zaakceptować klonowalne pochodne półklonowalnych pochodnych typu podstawowego.
supercat
@ Mauricio Scheffer: BTW, sugerowałbym, że ICloneable (z T) obsługuje również własność tylko do odczytu (typu zwracanego T). Pozwoliłoby to metodzie zaakceptować ICloneable (Foo) i używać go (za pośrednictwem właściwości Self) jako Foo.
supercat
9

Czytając ostatnio artykuł Dlaczego kopiowanie obiektu jest straszne? Myślę, że to pytanie wymaga dodatkowego wyjaśnienia. Inne odpowiedzi tutaj zawierają dobre porady, ale odpowiedź nie jest kompletna - dlaczego nie ICloneable<T>?

  1. Stosowanie

    Więc masz klasę, która to implementuje. Podczas gdy wcześniej miałeś metodę, która chciała ICloneable, teraz musi być ogólna, aby ją zaakceptować ICloneable<T>. Musisz go edytować.

    Następnie możesz mieć metodę sprawdzającą, czy obiekt is ICloneable. Co teraz? Nie możesz tego zrobić, is ICloneable<>a ponieważ nie znasz typu obiektu w typie kompilacyjnym, nie możesz uczynić metody ogólną. Pierwszy prawdziwy problem.

    Więc musisz mieć jedno ICloneable<T>i drugie ICloneable, wdrażające drugie. Dlatego implementator musiałby wdrożyć obie metody - object Clone()i T Clone(). Nie, dziękuję, mamy już dość zabawy IEnumerable.

    Jak już wspomniano, istnieje również złożoność dziedziczenia. Chociaż kowariancja może wydawać się rozwiązać ten problem, typ pochodny musi zaimplementować ICloneable<T>swój własny typ, ale istnieje już metoda o tej samej sygnaturze (= parametry, w zasadzie) - Clone()klasy podstawowej. Wyraźne określenie nowego interfejsu metody klonowania jest bezcelowe, stracisz przewagę, której szukałeś podczas tworzenia ICloneable<T>. Dodaj newsłowo kluczowe. Ale nie zapominaj, że musiałbyś również przesłonić klasę podstawową ” Clone()(implementacja musi pozostać jednolita dla wszystkich klas pochodnych, tj. Aby zwrócić ten sam obiekt z każdej metody klonowania, więc podstawową metodą klonowania musi być virtual)! Ale niestety nie można zarówno overrideinewmetody z tym samym podpisem. Wybierając pierwsze słowo kluczowe, stracisz cel, który chciałeś mieć podczas dodawania ICloneable<T>. Wybierając drugi, zepsułbyś sam interfejs, tworząc metody, które powinny robić to samo, zwracając różne obiekty.

  2. Punkt

    Chcesz ICloneable<T>wygody, ale komfort nie jest tym, do czego przeznaczone są interfejsy, ich znaczenie to (ogólnie OOP) ujednolicenie zachowania obiektów (chociaż w C # ogranicza się do ujednolicenia zachowania zewnętrznego, np. Metod i właściwości, a nie ich działania).

    Jeśli pierwszy powód Cię jeszcze nie przekonał, możesz sprzeciwić się temu, że ICloneable<T>może również działać restrykcyjnie, aby ograniczyć typ zwracany z metody klonowania. Jednak paskudny programista może implementować ICloneable<T>tam, gdzie T nie jest typem, który go implementuje. Tak więc, aby osiągnąć swoje ograniczenie, możesz dodać ładne ograniczenie do parametru ogólnego:
    public interface ICloneable<T> : ICloneable where T : ICloneable<T>
    Z pewnością bardziej restrykcyjne niż ten bez where, nadal nie możesz ograniczyć tego, że T jest typem implementującym interfejs (możesz wywodzić się z ICloneable<T>innego typu który to implementuje).

    Widzisz, nawet ten cel nie zostałby osiągnięty (oryginał ICloneablerównież zawodzi, żaden interfejs nie może naprawdę ograniczyć zachowania klasy implementującej).

Jak widać, dowodzi to, że ogólny interfejs jest trudny do pełnego wdrożenia, a także naprawdę niepotrzebny i bezużyteczny.

Ale wracając do pytania, tak naprawdę szukasz komfortu podczas klonowania obiektu. Można to zrobić na dwa sposoby:

Dodatkowe metody

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

To rozwiązanie zapewnia zarówno wygodę, jak i zamierzone zachowanie użytkowników, ale jest również zbyt długie do wdrożenia. Jeśli nie chcemy mieć „wygodnej” metody zwracającej obecny typ, o wiele łatwiej jest ją mieć public virtual object Clone().

Zobaczmy więc „ostateczne” rozwiązanie - co w C # ma naprawdę na celu dać nam komfort?

Metody rozszerzenia!

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

Nazywa się Kopiuj, aby nie kolidować z bieżącymi metodami klonowania (kompilator preferuje metody zadeklarowane przez typ niż metody rozszerzenia). Obowiązuje classograniczenie prędkości (nie wymaga sprawdzania wartości zerowej itp.).

Mam nadzieję, że to wyjaśnia powód, dlaczego tego nie robić ICloneable<T>. Jednak zaleca się, aby ICloneablew ogóle nie wdrażać .

IllidanS4 chce powrotu Moniki
źródło
Załącznik nr 1: Jedynym możliwym zastosowaniem rodzaju ogólnego ICloneablejest dla typów wartości, w których może to obejść boksowanie metody klonowania i sugeruje, że masz wartość rozpakowaną. A ponieważ struktury mogą być klonowane (płytko) automatycznie, nie ma potrzeby ich implementacji (chyba że określisz, że oznacza to głęboką kopię).
IllidanS4 chce, aby Monica wróciła
2

Chociaż pytanie jest bardzo stare (5 lat od napisania tych odpowiedzi :) i zostało już udzielone, ale znalazłem ten artykuł dość dobrze odpowiada na pytanie, sprawdź tutaj

EDYTOWAĆ:

Oto cytat z artykułu, który odpowiada na pytanie (koniecznie przeczytaj cały artykuł, zawiera on inne interesujące rzeczy):

W Internecie istnieje wiele odniesień wskazujących na posta na blogu z 2003 roku autorstwa Brada Abramsa - wówczas zatrudnionego w firmie Microsoft - w którym omawiane są niektóre przemyślenia na temat ICloneable. Wpis na blogu można znaleźć pod tym adresem: Implementing ICloneable . Pomimo mylącego tytułu ten wpis na blogu wzywa do niewprowadzania ICloneable, głównie z powodu płytkiego / głębokiego zamieszania. Artykuł kończy się prostą sugestią: jeśli potrzebujesz mechanizmu klonowania, zdefiniuj własną metodologię klonowania lub kopiowania i upewnij się, że jasno dokumentujesz, czy jest to głęboka czy płytka kopia. Odpowiednim wzorem jest:public <type> Copy();

Sameh Deabes
źródło
1

Dużym problemem jest to, że nie mogliby ograniczyć T do tej samej klasy. Na przykład, co uniemożliwiłoby Ci to:

interface IClonable<T>
{
    T Clone();
}

class Dog : IClonable<JackRabbit>
{
    //not what you would expect, but possible
    JackRabbit Clone()
    {
        return new JackRabbit();
    }

}

Potrzebują ograniczenia parametrów, takiego jak:

interfact IClonable<T> where T : implementing_type
sheamus
źródło
8
To nie wydaje się tak złe jak class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
Nikola Novak
Naprawdę nie rozumiem na czym polega problem. Żaden interfejs nie może zmusić implementacji do racjonalnego zachowania. Nawet gdyby ICloneable<T>mógł ograniczyć się Tdo dopasowania do własnego typu, nie wymusiłoby to implementacji Clone()zwracania czegokolwiek zdalnie przypominającego obiekt, na którym został sklonowany. Ponadto, chciałbym zaproponować, że jeśli ktoś jest za pomocą interfejsu kowariancji, może być najlepiej mieć klasy, które implementują ICloneablebyć uszczelnione, że interfejs ICloneable<out T>obejmują Selfnieruchomości, które oczekuje się, aby powrócić sobie i ...
Supercat
... sprawić, by konsumenci obsadzili lub ograniczyli do ICloneable<BaseType>lub ICloneable<ICloneable<BaseType>>. BaseTypeW pytaniu powinny mieć protectedmetodę klonowania, który będzie nazywany przez typ, który implementuje ICloneable. Ten projekt dopuszcza możliwość, że ktoś może chcieć mieć a Container, a CloneableContainer, a FancyContainer, a CloneableFancyContainerten drugi jest użyteczny w kodzie, który wymaga klonowalnej pochodnej Containerlub który wymaga FancyContainer(ale nie obchodzi go, czy można go klonować).
supercat
Powodem, dla którego wolę taki projekt, jest to, że kwestia, czy dany typ można rozsądnie sklonować, jest nieco ortogonalna w stosunku do innych jego aspektów. Na przykład można mieć FancyListtyp, który można rozsądnie sklonować, ale pochodna może automatycznie utrzymywać swój stan w pliku dyskowym (określonym w konstruktorze). Nie można sklonować typu pochodnego, ponieważ jego stan byłby dołączony do stanu zmiennego singletona (pliku), ale nie powinno to wykluczać użycia typu pochodnego w miejscach, które wymagają większości funkcji, FancyListale nie potrzebują sklonować to.
supercat
+1 - ta odpowiedź jest jedyną z tych, które tu widziałem, która naprawdę odpowiada na pytanie.
IllidanS4 chce, aby Monica wróciła
0

To bardzo dobre pytanie ... Możesz jednak stworzyć własne:

interface ICloneable<T> : ICloneable
{
  new T Clone ( );
}

Andrey twierdzi, że jest to zły interfejs API, ale nie słyszałem o tym, żeby ten interfejs był przestarzały. I to zniszczyłoby mnóstwo interfejsów ... Metoda klonowania powinna wykonać płytką kopię. Jeśli obiekt zapewnia również głęboką kopię, można użyć przeciążonego klonu (bool deep).

EDYCJA: Wzór, którego używam do „klonowania” obiektu, przekazuje prototyp do konstruktora.

class C
{
  public C ( C prototype )
  {
    ...
  }
}

Usuwa to wszelkie potencjalne nadmiarowe sytuacje związane z implementacją kodu. BTW, mówiąc o ograniczeniach ICloneable, czy tak naprawdę nie do samego obiektu należy decyzja, czy należy wykonać płytki klon, czy głęboki klon, czy nawet częściowo płytki / częściowo głęboki klon? Czy naprawdę powinno nas to obchodzić, o ile obiekt działa zgodnie z przeznaczeniem? W niektórych przypadkach dobre wdrożenie klonowania może równie dobrze obejmować zarówno płytkie, jak i głębokie klonowanie.

Baretta
źródło
„zły” nie oznacza przestarzały. To po prostu oznacza „lepiej nie używać tego”. Nie jest przestarzałe w tym sensie, że „usuniemy interfejs ICloneable w przyszłości”. Po prostu zachęcają cię do nieużywania go i nie aktualizują go za pomocą ogólnych lub innych nowych funkcji.
czerwiec
Dennis: Zobacz odpowiedzi Marca. Problemy z kowariancją / dziedziczeniem sprawiają, że interfejs ten jest praktycznie bezużyteczny w każdym scenariuszu, który jest co najmniej nieznacznie skomplikowany.
Tamas Czinege
Tak, widzę ograniczenia ICloneable, na pewno ... Jednak rzadko muszę korzystać z klonowania.
baretta