2013-03-08 11 views
6

Widziałem dziwne trochę zachowania w niektórych kod C#, że nie jestem w stanie wyjaśnić. Możliwe, że brakuje mi ważnej wiedzy, więc mam nadzieję, że ktoś może włączyć światło dla mnie.C# nie można ustawić właściwość wewnątrz IEnumerable

Got blok kodu, który wygląda tak:

IEnumberable<myObject> objects = GetObjectsFromApiCall(); 

    for (int i = 0; i < objects.Count(); i++) 
     { 
      if (String.IsNullOrEmpty(objects.ElementAt(i).SubObject.Title)) 
      { 
       SubObject sub = GetSubObjectFromDatabase((long)objects.ElementAt(i).SubObject.Id); 
       if (sub != null) 
       { 
        objects.ElementAt(i).SubObject.Title = sub.Title; 
       } 
      } 
     } 

Kiedy krok przez to wszystko, co na temat tego kodu wydaje się działać poprawnie. Kolekcja "obiektów" jest zapełniana zgodnie z oczekiwaniami. "pod" jest pobierane jako zebrane i ma pełny zestaw oczekiwanych właściwości, w tym wypełnioną właściwość tytułu. Podczas wykonywania nie są zgłaszane żadne błędy.

Ale ... właściwość SubObject.Title (która ma tylko standard get, set; code), która istnieje w każdym obiekcie uparcie pozostaje pusta.

Brakuje mi. Ktoś wyjaśnia, co się dzieje?

EDYCJA: Dla tych, którzy zasugerowali, że nie powinienem używać pętli for i ElementAt, zacząłem od pętli foreach, ale pomyślałem, że to może być źródłem problemu, ponieważ za każdym razem pobierało nowe SubObject. Naprawiono teraz, dzięki twojej pomocy i ForEachowi przywrócono.

Cheers, Matt

+4

Gotowe: [Aktualizowanie właściwości elementu w ramach opcji IEnumerable, ale właściwość nie pozostaje ustawiona?] (Http://stackoverflow.com/a/9104212/93732) –

+0

Ten kod może być tak powolny, nawet nie jest zabawne. – ChaosPandion

+0

Czy możesz skopiować/wkleić * aktualny * kod, który posiadasz, a nie coś, co * wygląda * jak * rzeczywisty * kod? – ken2k

Odpowiedz

4

chciałbym naprawić to w ten sposób:

var objects = GetObjectsFromApiCall().ToList(); 

Następnie można zachować pętlę jak jest (to działa), lub zoptymalizować go trochę, używając foreach i niektóre Linq sugerowane przez innych odpowiedzi, ale to robi nie ma to znaczenia: problem polegał na tym, że próbowałeś zmienić element na IEnumeratorze <> jak wyjaśniono w this question wskazanym przez @Ahmet Kakıcı.

+1

-1 To jest rzeczywiście błędne. Nie ma problemu z modyfikowaniem elementów zwracanych przez 'IEnumerable'. Właściwie, kiedy używasz 'foreach' po' ToList() ', właśnie używasz' IEnumerable', jako 'List ' implementuje 'IEnumerable '. Jednym z problemów może być * opóźniona realizacja * zapytania DB, ale z pewnością nie jest to spowodowane prostą obecnością interfejsu 'IEnumerable' ... – ken2k

+0

Masz rację, czytam zbyt szybko ... Problemem nie jest IEnumerable ale sposób w jaki jest realizowany. Dlatego używanie ToList() ma sens. Dziękuję za wyjaśnienie. – Larry

+0

@ ken2k Prawidłowe. Zobacz moją odpowiedź dla przykładów, w których modyfikuję elementy zwracane przez 'IEnumerable'. –

1

Przede wszystkim, nie należy używać ElementAt() dla tego rodzaju kodu, użyj

foreach (var o in objects) 
{ 
    if (string.IsNullOrEmpty(o.SubObject.Title)) 
    { 
     o.SubObject.Title = ...; 
    } 
} 

Ponadto należy pamiętać, że jeśli metoda zwraca dynamiczny IEnumerable następnie za każdym razem, zadzwonić pod numer objects.Something() ponownie wywoływany jest interfejs API i pobierana jest nowa kopia. W takim przypadku powinieneś skopiować przeliczalne na listę używając metody .ToList().

Istnieje również sposób nie umieszczenie kopii w liście - poprzez stworzenie dynamicznego wyliczający tak:

objects = objects.Select(o => 
{ 
    if (string.IsNullOrEmpty(o.SubObject.Title)) 
    { 
     o.SubObject.Title = ...; 
    } 
    return o; 
}); 

chodzi o wartości nie są ustawione prawidłowo (jeśli poprzednia rzeczy nie pomogło) - spróbuj dodać throw new Exception(value) w ustawieniu dla właściwości Title - zobacz, czy jest wywoływana z poprawną wartością.

+0

"Po pierwsze, nie powinieneś używać ElementAt() do tego rodzaju kodu." Czemu? –

+0

.NET będzie wyliczać za każdym razem przy użyciu 'Enumerator.MoveNext()' dla 'i' razy, aby uzyskać wartość. jest wolniejsze niż podejście 'list [i]'. –

2

Spróbuj

List<myObject> objects = GetObjectsFromApiCall().ToList(); 

foreach(var obj in objects.Where(o => string.IsNullOrEmpty(objects.SubObject.Title)).ToList()) 
{ 
    var subObject = GetSubObjectFromDatabase(obj.SubObject.Id); 
    if(subObject == null) continue; 

    obj.SubObject.Title = subObject.Title; 
} 
1

I Gości The GetObjectsFromApiCall funkcja wygląda jak następuje:

public IEnumberable<myObject> GetObjectsFromApiCall(){ 
    for(var i = 0; i < 10; i++) 
    { 
     yield return new myObject(); 
    } 
} 

Jeśli mam rację, za każdym razem dzwonić objects.ElementAt (i) funkcji, aby uzyskać obiekt , otrzymasz nowy obiekt przez "yield return new myObject()".

+0

To dobra teoria. Myślałem o zamieszczeniu takiego przykładu. –

+0

oh, powinieneś zmienić "objects.ElementAt (i) .SubObject.Title = sub.Title;" do "var obj = objects.ElementAt (i) .SubObject; obj.Title = sub.Title;" – fengyj

+0

Odnośnie komentarza: co by to zmieniło? –

1

Ale jak sprawdzić, czy zmieniono właściwość Title? Czy dzwonisz ponownie pod numer GetObjectsFromApiCall()? Czy możesz ponownie foreach przez tę samą instancję objects?

Instancja IEnumerable może tworzyć i dawać nowe obiekty za każdym razem, gdy jest "wyliczana". Oto prosty przykład ilustracji. Na przykład określić:

class SomeObject 
{ 
    public string Title { get; set; } 
} 

Następnie rozważymy dwa typy „Źródło”, pierwszą tablicę, a następnie blok iterator zdefiniowany następująco:

static IEnumerable<SomeObject> GetSomeSequence() 
    { 
     yield return new SomeObject { Title = "Alpha", }; 
     yield return new SomeObject { Title = "Beta", }; 
     yield return new SomeObject { Title = "Gamma", }; 
    } 

następnie przetestować go w ten sposób:

static void Main() 
    { 
     IEnumerable<SomeObject> thingsToModify; 

     // set source to an array 
     thingsToModify = new[] { new SomeObject { Title = "Alpha", }, new SomeObject { Title = "Beta", }, new SomeObject { Title = "Gamma", }, }; 

     foreach (var t in thingsToModify) 
      Console.WriteLine(t.Title); 

     foreach (var t in thingsToModify) 
      t.Title = "Changed!"; 

     foreach (var t in thingsToModify) 
      Console.WriteLine(t.Title); // OK, modified 


     // set source to something which yields new object each time a new GetEnumerator() call is made 
     thingsToModify = GetSomeSequence(); 

     foreach (var t in thingsToModify) 
      Console.WriteLine(t.Title); 

     foreach (var t in thingsToModify) 
      t.Title = "Changed!";   // no-one keeps these modified objects 

     foreach (var t in thingsToModify) 
      Console.WriteLine(t.Title); // new objects, titles not modified 

    } 

Wniosek: Jest całkowicie możliwa modyfikacja stanu zmiennego obiektu, który należy do źródła, nad którym się znajdujemy. Jednak niektóre typy źródeł IEnumerable dają nowe kopie danych za każdym razem, gdy są wywoływane, a następnie nie ma sensu wprowadzanie modyfikacji do kopii.

Powiązane problemy