2008-12-11 17 views
8

Dlaczego listy list1Instance i p w metodzie Main z poniższego kodu wskazują na tę samą kolekcję?Jak zrobić IEnumerable <T> readonly?

class Person 
    { 
     public string FirstName = string.Empty; 
     public string LastName = string.Empty; 

     public Person(string firstName, string lastName) { 
      this.FirstName = firstName; 
      this.LastName = lastName; 
     } 
    } 

    class List1 
    { 
     public List<Person> l1 = new List<Person>(); 

     public List1() 
     { 
      l1.Add(new Person("f1","l1")); 
      l1.Add(new Person("f2", "l2")); 
      l1.Add(new Person("f3", "l3")); 
      l1.Add(new Person("f4", "l4")); 
      l1.Add(new Person("f5", "l5")); 
     } 
     public IEnumerable<Person> Get() 
     { 
      foreach (Person p in l1) 
      { 
       yield return p; 
      } 

      //return l1.AsReadOnly(); 
     } 

    } 

    class Program 
    { 

     static void Main(string[] args) 
     { 
      List1 list1Instance = new List1(); 

      List<Person> p = new List<Person>(list1Instance.Get());   

      UpdatePersons(p); 

      bool sameFirstName = (list1Instance.l1[0].FirstName == p[0].FirstName); 
     } 

     private static void UpdatePersons(List<Person> list) 
     { 
      list[0].FirstName = "uf1"; 
     } 
    } 

Czy możemy zmienić to zachowanie bez zmiany typu zwrotu na List1.Get()?

Dzięki

Odpowiedz

26

W rzeczywistości IEnumerable<T>jest już tylko do odczytu. Oznacza to, że nie można zastąpić żadnych pozycji w podstawowej kolekcji różnymi przedmiotami. Oznacza to, że nie można modyfikować obiektów w obiektach przechowywanych w kolekcji. Typ Person nie jest jednak tylko do odczytu, a ponieważ jest to typ odniesienia (tj. class), można zmieniać jego członków poprzez odniesienie.

są dwa rozwiązania:

  • użyć struct jako typ powrotnej (który tworzy kopię wartości za każdym razem, jest zwracana, a więc pierwotna wartość nie zostanie zmieniony   —, które mogą być kosztowne nawiasem mówiąc)
  • Aby wykonać to zadanie, należy użyć właściwości tylko do odczytu typu Person.
+4

Ale możesz odrzucić dowolne 'IEnumerable ' z powrotem na 'T []', 'List ', lub jakikolwiek jest on w rzeczywistości i modyfikować jego elementy (same, a nie ich właściwości). – Shimmy

+2

@Shimmy Jeśli mamy uzyskać informacje techniczne na temat tego, co możesz * możesz * zrobić, możesz również użyć refleksji, aby uzyskać dostęp do prywatnych członków klasy i robić, co chcesz. Tylko dlatego, że * możesz * zrobić coś (np. Rzucić 'IEnumerable' do jego rzeczywistego typu) nie oznacza, że ​​** zawsze ** powinien. – Alex

+3

'IEnumerable ' nie jest w gruncie rzeczy zbiorem tylko do odczytu. – Shimmy

2

Nie wskazują one tej samej kolekcji .Net, lecz raczej na te same obiekty Person. Linia:

List<Person> p = new List<Person>(list1Instance.Get()); 

kopiuje wszystkie elementy osoba z list1Instance.Get() do listy p. Słowo "kopie" oznacza tutaj kopie odniesień. Tak więc twoja lista i IEnumerable tylko wskazują na te same obiekty Person.

IEnumerable<T> jest zawsze readonly, z definicji. Jednak obiekty wewnątrz mogą być zmienne, tak jak w tym przypadku.

7

Zwróć nową instancję osoby, która jest kopią p zamiast samej p w Get(). Będziesz potrzebował metody, aby wykonać głęboką kopię obiektu Person, aby to zrobić. Nie spowoduje to, że będą one tylko do odczytu, ale będą inne niż na pierwotnej liście.

public IEnumerable<Person> Get() 
{ 
    foreach (Person p in l1) 
    { 
     yield return p.Clone(); 
    } 
} 
0

IEnumerable<T>jest readonly

p to nowa kolekcja, która nie zależy od list1instance. Błąd, który popełniłeś, polega na tym, że pomyślałeś, że linia ta zmodyfikowałaby tylko jedną z list, podczas gdy faktycznie modyfikujesz obiekt Person.
Dwie kolekcje są różne, po prostu mają te same przedmioty.
Aby udowodnić, że są różne, spróbuj dodać i usunąć elementy z jednej z list, a zobaczysz, że nie ma to wpływu na drugi.

0

Po pierwsze, twoja lista w klasie jest publiczna, więc nic nie powstrzyma nikogo przed bezpośrednim dostępem do samej listy.

Po drugie, chciałbym zaimplementować IEnumerable i powrót w moim metody GetEnumerator

return l1.AsReadOnly().GetEnumerator(); 
1

Można zrobić deepclone każdej pozycji na liście, i nigdy nie wrócić odniesień do oryginalnych przedmiotów.

public IEnumerable<Person> Get() 
{ 
    return l1 
    .Select(p => new Person(){ 
     FirstName = p.FirstName, 
     LastName = p.LastName 
    }); 
} 
0

Jeśli obiekt osoby jest obiektem rzeczywistym, należy rozważyć użycie wersji niezmiennej.

public class Person 
{ 
    public FirstName {get; private set;} 
    public LastName {get; private set;} 
    public Person(firstName, lastName) 
    { 
     FirstName = firstName; 
     LastName = lastName; 
    } 
    } 

W ten sposób nie można jej zmienić zawartość instancji raz stworzył i dlatego nie jest ważne, że istniejące instancje są ponownie wykorzystywane w wielu listach.

Powiązane problemy