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ę.
c#
.net
icloneable
Bluenuance
źródło
źródło
Odpowiedzi:
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.
źródło
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śliCloneableFoo
dziedziczy z [nie publicznie Cloneable]Foo
, powinnyCloneableDerivedFoo
pochodzić z ...identity
na 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ącClone
. 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.Clone
stworzy 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ć,ShallowCopy
aby podkreślić punkt, albo wcale.Oprócz odpowiedzi Andreya (z którą się zgadzam, +1) - kiedy
ICloneable
to zrobisz, możesz także wybrać jawną implementację, aby publicznyClone()
zwrot był typowanym obiektem:Oczywiście istnieje druga kwestia z
ICloneable<T>
dziedziczeniem rodzajowym .Jeżeli mam:
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?źródło
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)
źródło
ICloneable<out T>
może być całkiem przydatna, jeśli zostanie odziedziczonaISelf<out T>
za pomocą jednej metodySelf
typuT
. Często nie potrzeba „czegoś, co można klonować”, ale równie dobrze może potrzebować czegoś, co możnaT
klonować. Jeśli implementuje się klonowalny obiektISelf<itsOwnType>
, procedura, która wymagaT
klonowalnego, może zaakceptować parametr typuICloneable<T>
, nawet jeśli nie wszystkie klonowalne pochodne tego samegoT
przodka.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ć, coFoo
zawiera jakiś rodzaj, ale nie będzie go mutować ani oczekiwać, że nigdy się nie zmieni, mógłby użyćIReadableFoo
, podczas gdy ...Foo
może użyćImmutableFoo
kodu while, który chce nim manipulować, może użyćMutableFoo
. Podany kod dowolnego typuIReadableFoo
powinien 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 zICloneable<T>
niezmienną kopią klasy, która przechowujeT
„.List<T>
, tak, że sklonowanaList<T>
jest nowa kolekcja zawierająca wskaźniki do wszystkich tych samych obiektów w oryginalnej kolekcji, istnieją dwa proste sposoby, aby to zrobić bezICloneable<T>
. Pierwsza toEnumerable.ToList()
metoda rozszerzenia:List<foo> clone = original.ToList();
druga toList<T>
konstruktor, który przyjmujeIEnumerable<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. ;)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.
źródło
W razie potrzeby napisanie interfejsu jest bardzo proste :
źródło
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>
?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 drugieICloneable
, wdrażające drugie. Dlatego implementator musiałby wdrożyć obie metody -object Clone()
iT Clone()
. Nie, dziękuję, mamy już dość zabawyIEnumerable
.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 tworzeniaICloneable<T>
. Dodajnew
sł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ównooverride
inew
metody z tym samym podpisem. Wybierając pierwsze słowo kluczowe, stracisz cel, który chciałeś mieć podczas dodawaniaICloneable<T>
. Wybierając drugi, zepsułbyś sam interfejs, tworząc metody, które powinny robić to samo, zwracając różne obiekty.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ę zICloneable<T>
innego typu który to implementuje).Widzisz, nawet ten cel nie zostałby osiągnięty (oryginał
ICloneable
ró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
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!
Nazywa się Kopiuj, aby nie kolidować z bieżącymi metodami klonowania (kompilator preferuje metody zadeklarowane przez typ niż metody rozszerzenia). Obowiązuje
class
ograniczenie 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ę, abyICloneable
w ogóle nie wdrażać .źródło
ICloneable
jest 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ę).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):
źródło
Dużym problemem jest to, że nie mogliby ograniczyć T do tej samej klasy. Na przykład, co uniemożliwiłoby Ci to:
Potrzebują ograniczenia parametrów, takiego jak:
źródło
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
mógł ograniczyć sięT
do dopasowania do własnego typu, nie wymusiłoby to implementacjiClone()
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ąICloneable
być uszczelnione, że interfejsICloneable<out T>
obejmująSelf
nieruchomości, które oczekuje się, aby powrócić sobie i ...ICloneable<BaseType>
lubICloneable<ICloneable<BaseType>>
.BaseType
W pytaniu powinny miećprotected
metodę klonowania, który będzie nazywany przez typ, który implementujeICloneable
. Ten projekt dopuszcza możliwość, że ktoś może chcieć mieć aContainer
, aCloneableContainer
, aFancyContainer
, aCloneableFancyContainer
ten drugi jest użyteczny w kodzie, który wymaga klonowalnej pochodnejContainer
lub który wymagaFancyContainer
(ale nie obchodzi go, czy można go klonować).FancyList
typ, 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,FancyList
ale nie potrzebują sklonować to.To bardzo dobre pytanie ... Możesz jednak stworzyć własne:
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.
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.
źródło