2012-12-20 11 views
10

Mam listę, która zostanie wypełniona niektóre dane z operacji i przechowuje go w pamięci podręcznej pamięci. Teraz chcę kolejną listę, która zawiera pewne podrzędne dane z listy na podstawie pewnych warunków.Jak zmienić moją nową listę bez zmiany oryginalnej listy?

Jak widać w poniższym kodzie wykonuję operację na liście docelowej. Problem polega na tym, że wszelkie zmiany, które robię na liście celów, są również wykonywane na liście głównej. Myślę, że to z powodu odniesienia jest takie samo lub coś takiego.

Wystarczy, że operacja na liście docelowej nie wpłynie na dane na głównej liście.

List<Item> target = mainList; 
SomeOperationFunction(target); 

void List<Item> SomeOperationFunction(List<Item> target) 
{ 
    target.removeat(3); 
    return target; 
} 
+1

Czy mówisz, że obiekty obecne na obu listach są modyfikowane? To znaczy. czy potrzebujesz Klonowania()/tworzenia ich kopii, zamiast pracować nad tymi samymi instancjami? – Oli

Odpowiedz

14

trzeba sklonować swoją listę w swojej metodzie, ponieważ List<T> jest klasa, więc to odniesienie typu i jest przekazywane przez referencję.

Na przykład:

List<Item> SomeOperationFunction(List<Item> target) 
{ 
    List<Item> tmp = target.ToList(); 
    tmp.RemoveAt(3); 
    return tmp; 
} 

Albo

List<Item> SomeOperationFunction(List<Item> target) 
{ 
    List<Item> tmp = new List<Item>(target); 
    tmp.RemoveAt(3); 
    return tmp; 
} 

lub

List<Item> SomeOperationFunction(List<Item> target) 
{ 
    List<Item> tmp = new List<Item>(); 
    tmp.AddRange(target); 
    tmp.RemoveAt(3); 
    return tmp; 
} 
+0

Opcja select nic tu nie robi, potrzebujesz tylko 'ToList'. – Servy

+0

Perfect..Thank you – user505210

+1

Trzeci przykład jest po prostu całkowicie błędny. Zmienna 'tmp' jest używana do zapełniania się. Wtedy nadal usuwasz element z 'target' zamiast' tmp''. –

1

Twoja zmienna docelowa jest typem odniesienia. Oznacza to, że wszystko, co do niego zrobisz, zostanie odzwierciedlone na liście, którą do niego przejdziesz.

Aby tego nie zrobić, musisz utworzyć nową listę w metodzie, skopiować do niej zawartość target, a następnie wykonać operację usunięcia podczas operacji na nowej liście.

About Reference and Value Types

4

zbudować nową listę pierwszy i działają na tym, bo lista jest typ referencyjny, czyli kiedy przekazać go w funkcji, nie tylko przekazuje wartość, ale sam obiekt.

Jeśli tylko przypisać target do mainList, obie zmienne wskazują na ten sam przedmiot, więc trzeba utworzyć nową listę:

List<Item> target = new List<Item>(mainList); 

void List<Item> SomeOperationFunction() nie ma sensu, bo albo ty nic (void) zwrotu lub zwrócisz List<T>. Więc albo usuń instrukcję return ze swojej metody, albo zwróć nową List<Item>. W tym ostatnim przypadku, przepisałbym to jako:

List<Item> target = SomeOperationFunction(mainList); 

List<Item> SomeOperationFunction(List<Item> target) 
{ 
    var newList = new List<Item>(target); 
    newList.RemoveAt(3); 
    return newList; 
} 
+0

'List jest typem odniesienia i dlatego przekazywany przez referencję' - jest okrągły. Jeśli to powiesz, będziesz musiał wyjaśnić, co to jest typ odniesienia, lub link do referencji (gra słów nie jest przeznaczona). –

+0

@RobertHarvey Dzięki za wskazanie tego. – Residuum

+0

Oczywiście nadal przekazujesz referencję według wartości, a nie przez odniesienie, chyba że masz słowo kluczowe "ref". Ważne jest, aby odróżnić przekazywanie odniesienia przez wartość w stosunku do wartości przez odniesienie. – Servy

6

Musisz zrobić kopię listy, aby zmiany w kopii nie wpłynęły na oryginał. Najprostszym sposobem jest użycie metody ToList w postaci System.Linq.

var newList = SomeOperationFunction(target.ToList()); 
+3

@Bernhof: To prawda; należy jednak pamiętać, że lista nadal zawiera odniesienia do tych samych elementów, jeśli elementy mają typ odniesienia. Zatem zmiany samych przedmiotów będą miały wpływ na pozycje na obu listach. –

+0

Niewielki punkt do zrobienia, ale twierdzę, że 'SomeOperationFunction' powinien obsługiwać kopiowanie. Można by się tego spodziewać, gdyby funkcja * zwracała * a 'List '. – bernhof

+0

@Bernhof Prawdopodobnie, tak. Biorąc pod uwagę, że podobnie jak 5 innych osób zasugerowało tę zmianę, nie widziałem potrzeby jej przekształcania. Chodzi tylko o to, że kopiowanie musi gdzieś się odbyć. – Servy

0

Twój Obserwujemy pierwotną listę modyfikowane ponieważ domyślnie jakieś non-prymitywne obiekty są przekazywane przez referencję (To jest rzeczywiście przechodzą przez wartości, wartość odniesienia, ale to inna sprawa).

Co musisz zrobić, to sklonować obiekt.To pytanie pomoże ci z jakimś kodem do sklonowania listy w C#: How do I clone a generic list in C#?

+1

Nie ma znaczenia, jeśli lista jest przekazywana przez odniesienie lub wartość. W każdym przypadku "target" będzie odniesieniem wskazującym na oryginalną listę. Przez wartość/przez odniesienie robi różnicę tylko wtedy, gdy przypisujesz nową wartość do 'cel' jak w' target = null; '. –

+0

@ OlivierJacot-Descombes - Dlatego uprośniłem, mówiąc, że jest to odniesienie. Komentarz w nawiasach jest po prostu stwierdzeniem, że wszystkie zmienne w C# są przekazywane przez wartość, chyba że wyraźnie użyjesz słowa kluczowego "ref". Tak się składa, że ​​"wartością" używaną dla typów złożonych jest wskaźnik. –

+0

'ciąg' jest typem pierwotnym, ale jest typem odniesienia (jest niezmienny). 'System.Drawing.Point' nie jest typem pierwotnym, ale jest typem wartości (struct). –

0

Ponieważ typ List jest typem referencyjnym, to funkcja przekazywana jest do oryginalnej listy.

Zobacz ten MSDN article, aby uzyskać więcej informacji na temat przekazywania parametrów w języku C#.

Aby osiągnąć to, co chcesz, powinieneś utworzyć kopię listy w SomeOperationFunction i zwrócić ją w zamian. Prosty przykład:

void List<Item> SomeOperationFunction(List<Item> target) 
{ 
    var newList = new List<Item>(target); 
    newList.RemoveAt(3); 
    return newList; // return copy of list 
} 

Jak podkreśla Olivier Jacot-Descombes w komentarzach do innej odpowiedzi, ważne jest, aby pamiętać, że

[...] lista nadal posiada referencje do tych samych pozycji, jeśli pozycje są typu referencyjnego. Zatem zmiany samych pozycji będą nadal wpływać na elementy na obu listach.

+1

Może powinieneś poświęcić trochę czasu na przeczytanie własnego linku. Typy referencyjne nie są przekazywane przez referencje, są przekazywane przez wartość, podobnie jak typy wartości, ale to, co przekazano *, jest referencją *. To bardzo ważna różnica. – Servy

+0

"Roztrzaskuj literówkę, Rozumiem różnicę i zredagowałem odpowiedź, aby uniknąć nieporozumień. – bernhof

+0

Dlaczego usunąłeś link? To było wystarczająco dobre połączenie. – Servy

0

Zamiast przypisywania MainList do cel, chciałbym zrobić: target.AddRange(mainList);

Wtedy będziesz mieć kopię przedmiotów zamiast odniesienia do listy.

+0

Dlaczego wywołać 'ToArray'? 'AddRange' pobiera' IEnumerable', który lista już implementuje. – Servy

+0

Uruchomiłem instancje (być może .net 2.0 lub 3.5), w których 'AddRange' pobierało tylko tablicę. Cieszę się, że mogę zmodyfikować moją odpowiedź, jeśli najnowszy framework akceptuje 'IEnumerable' –

+0

' Przeciążenie zostało dodane z .NET 2.0. [link] (http://msdn.microsoft.com/en-us/library/z883w3dc (v = vs.80) .aspx). Jedyną wersją, która jej nie posiadała była 1.0. – Servy

0

Wystarczy, że zainicjujesz nową listę z listą utworzoną przez skopiowanie elementów z listy źródłowej.

List<Item> target = mainList; Powinny być List<item> target = new List<Item>(mainList);

0

Musisz zrobić kopię listy, ponieważ w oryginalnym kodzie co robisz jest po prostu przechodząc wokół, jak słusznie podejrzewał, odniesienie (ktoś by to nazwać wskaźnik).

Można też wywołać konstruktor na nowej liście, przekazując oryginalne listy jako parametru:

List<Item> SomeOperationFunction(List<Item> target) 
{ 
    List<Item> result = new List<Item>(target); 
    result.removeat(3); 
    return result; 
} 

Albo stworzyć MemberWiseClone:

List<Item> SomeOperationFunction(List<Item> target) 
{ 
    List<Item> result = target.MemberWiseClone(); 
    result.removeat(3); 
    return result; 
} 

Ponadto, nie są przechowywane powrót SomeOperationFunction w dowolnym miejscu, więc możesz chcieć również poprawić tę część (zadeklarowałeś metodę jako void, która nie powinna niczego zwracać, ale wewnątrz niej zwracasz obiekt). powinien wywołać metodę w ten sposób:

List<Item> target = SomeOperationFunction(mainList); 

Uwaga: elementy listy nie zostaną skopiowane (tylko ich odniesienia kopiowane), więc zmieniając stan wewnętrzny elementów wpłynie zarówno listy.

0

Nawet jeśli utworzysz nową listę, odniesienia do pozycji na nowej liście będą nadal wskazywać pozycje na starej liście, więc chciałbym użyć tej metody rozszerzenia, jeśli potrzebuję nowej listy z nowymi referencjami. ..

public static IEnumerable<T> Clone<T>(this IEnumerable<T> target) where T : ICloneable 
{ 
    If (target.IsNull()) 
     throw new ArgumentException(); 

    List<T> retVal = new List<T>(); 

    foreach (T currentItem in target) 
     retVal.Add((T)(currentItem.Clone())); 

    return retVal.AsEnumerable(); 
} 
Powiązane problemy