[C#] Deep Copy Lista


(Marcin Obala) #1

Witam

Chciałbym wykonać kopię listy w swoim programie jednak nie chce to działać.

Mam w programie dwie listy, łączę je, następnie wykonuje operacje na nowej połączonej liście. Jednak nie chcę wprowadzać modyfikacji w tych dwóch listach składowych.

Kiedy próbuję zrobić tak

List Merged = new List();

Merged.AddRange(List1);

Merged.AddRange(List2);

Jednak wykonując operacje na liście Merged zmieniam również listy LIst1 i List2. Próbowałem też tak:

List CopyList1 = new List(List1);

List CopyList2 = new List(List2);

List Merged = new List();

Merged.AddRange(CopyList1);

Merged.AddRange(CopyList2);

Jednak to na nic bo zmieniając coś w Merged modyfikują się też CopyList oraz List. W jaki sposób prawidłowo wykonać kopię listy? Myślałem o zwykłej funkcji w swojej klasie która utworzy nową listę a następnie ustawi odpowiednie wartości na polach tej klasy. Jednak to wydaje mi się zbyt czasochłonne robić kolejne pętle.

Pozdrawiam.


(Tomek Matz) #2

Nie wiem, czy już sobie poradziłeś. Jeśli nie, to: Tworzenie obiektów klonowalnych (ICloneable) (strona 287) :slight_smile: Jak już zaimplementujesz ten interfejs w swojej klasie, to możesz dodać do kodu taką metodę rozszerzającą:

static class ListExtensions

{

        public static IList Clone(this IList listToClone) where T: ICloneable

        {

                return listToClone.Select(item => (T)item.Clone()).ToList();

        }

}

http://stackoverflow.com/questions/222598/how-do-i-clone-a-generic-list-in-cEDIT: Jeszcze możesz sobie przejrzeć ten temat http://stackoverflow.com/questions/536349/why-no-icloneablet. Poruszają tutaj problem tego czemu nie ma ICloneable oraz czy można coś z tym zrobić. Jednym z sugerowanych rozwiązań jest stworzenie własnego interfejsu dziedziczącego po ICloneable, choć mi osobiście najbardziej podoba się to rozwiązanie:

public Foo Clone() { /* your code */ }

object ICloneable.Clone() {return Clone();}

W każdym bądź razie warto się z tym zapoznać.


(Marcin Obala) #3

Oczywiście mam zamiar się z tym zapoznać. Nic nie robiłem póki co ale zanim się z tym zapoznam pewnie przerobię funkcję tak aby zapisywało do dwóch list na raz.

List1 i List2 są wypełniane w zależności od pewnej zmiennej. Dla uproszczenia załóżmy że jest to zmienna bool i jeśli true to dopisujemy do List1 a jeśli false to do List2. Na początek po prostu dodam w pętli dodam żeby zawsze też wpisywało w Merged.


(somekind) #4

Najprostszym chyba sposobem klonowania obiektów jest serializacja, nie trzeba się wówczas bawić w pętle i ręczne kopiowanie wszystkich właściwości:

public class UniversalCloner

{

    public static T DeepClone(T obj)

    {

        object objResult = null;

        using (MemoryStream ms = new MemoryStream())

        {

            BinaryFormatter bf = new BinaryFormatter();

            bf.Serialize(ms, obj);

            ms.Position = 0;

            objResult = bf.Deserialize(ms);

        }


        return (T)objResult;

    }

}

(Tomek Matz) #5

Sam kod wygląda nieźle, a jak z wydajnością takiego rozwiązania (zakładam tutaj, że ktoś musi wykonać dużo takich klonowań) ?


(kostek135) #6

Serializacja to nie za bardzo służy do klonowania obiektów (przynajmniej w javie). Przychylam się do rozwiązania matzu. Nie wiem jak w c# w javie każdy obiekt ma metodę clone(), która jest deklarowana w object, trzeba ją przedefiniować we własnym obiekcie i zaimplementować jak ma byc klonowany: głeboko, płytko...


(Tomek Matz) #7

@kostek135

Tzn. się to rozwiązanie, które zaproponował somekind da się połączyć z tym co ja zaproponowałem. To akurat nie stanowi problemu. Problem jaki tutaj widzę to przede wszystkim wydajność, a przynajmniej tak mi się wydaje. Sądzę, że rozwiązanie oparte o serializację będzie o wiele wolniejsze.

Swoją drogą przed chwilą zauważyłem inny problem z tym rozwiązaniem. Co jeśli jakieś pole zostanie oznaczone jako NonSerialized? Spowoduje to, że wartość tego pola nie zostanie przypisana w utworzonej kopii.


(somekind) #8

kostek135 , kod, który wkleiłem można przecież użyć w metodzie Clone. To, że serializacja niekoniecznie do tego służy - no cóż, to od programisty zależy, jakiego mechanizmu do czego używa.

matzu , podałem przykład najkrótszego w implementacji rozwiązania. Jeśli ktoś potrzebuje większej wydajności albo kontroli nad tym procesem, to niestety musi napisać ręcznie.


(Marcin Obala) #9

Tematu jeszcze nie przemieliłem. Co z tego że wkleję kod i będzie on działał skoro go nie będę rozumiał. Ledwo przez wstęp przebrnąłem w książce.


(Tomek Matz) #10

Ten pierwszy rozdział z książki (Filozofia platformy .NET) to jeden z trudniejszych. Później jest kilka bardziej przyjemnych. Ja Ci podałem numer strony, gdzie masz wyjaśnione o co chodzi z tym kopiowaniem (to jest tylko 4-5 stron, IMO w miarę przystępnie napisanych). Te 2 linki co wrzuciłem też są całkiem dobre (traktuj je jako rozszerzenie tych kilku stron). Na podstawie tych trzech źródeł coś na pewno naskrobiesz (później możesz tu wrzucić do przejrzenia/poprawy(?)).

Ten kod co podałeś na początku nie działa dlatego, że Ty w tych listach przechowujesz instancje typów referencyjnych. Gdy tworzyłeś te nowe listy w oparciu o elementy już istniejących list, to przekazywałeś do nich referencje, które wskazują na obiekty w pamięci, a nie kopie tych obiektów. Miałeś więc kilka list, które przechowywały te same referencje, a więc wskazywały na dokładnie te same obiekty w pamięci.

Gdyby te listy przechowywały typy wartościowe, np. int-y, string-i (string to taki specyficzny typ wartościowy; w rzeczywistości jest to typ referencyjny, który sprawia wrażenie jakby był wartościowy - w książce chyba też jest gdzieś o tym mowa), to ten kod działałby poprawnie. Gdy przekazujesz do metody typ wartościowy, to zawsze przekazujesz jego kopię.