2012-07-19 6 views
10

Napotkaliam kilka razy na błędy Collection was modified; enumeration operation may not execute podczas zwracania wyników kwerendy LINQ w funkcji, jak ta ... (Dodam, że funkcja działa jako implementacja interfejsu, a wyniki opuszczają ten moduł zostać wykorzystane w innym.)Czy powinienem zawsze wywoływać .ToArray na wyniki zapytania LINQ zwrócone w funkcji?

Public Function GetTheFuzzyFuzzbuzzes() As IEnumerable(of FuzzBuzz) _ 
    Implements IFoo.GetTheFuzzyFuzzBuzzes 

    Return mySecretDataSource.Where(Function(x) x.IsFuzzy) 
End Function 

mam, co do zasady, zawsze zadzwonić .ToArray po powrocie wynik LINQ kwerendy w funkcji lub właściwości getter jeśli dane źródłowe ma potencjał, aby być zmienione? Wiem, że jest w tym trochę wydajności, ale mam przeczucie, że należy to zrobić, dlatego należy zawsze robić to, aby uniknąć czasowych problemów z łączeniem.

Edit:

Pozwól mi zrobić lepiej wyjaśniający domeny problemu.

Mamy graficzną implementację naszego głównego obszaru zainteresowania, który jest problemem optymalizacyjnym. Elementy są reprezentowane jako węzły grafów. Krawędzie ważone różnymi kosztami i inne parametry wyrażają związki między węzłami. Gdy użytkownik manipuluje danymi, tworzymy różne krawędzie i oceniamy różne opcje, które mogą podjąć w stosunku do bieżącego stanu, aby uzyskać informacje zwrotne na temat wyników każdej opcji. Zmiany dokonane w danych na serwerze przez innych użytkowników i programy są natychmiast przekazywane do klienta za pośrednictwem technologii push. Używamy wielu wątków ...

... wszystko to oznacza, że ​​wiele rzeczy dzieje się w bardzo asynchroniczny sposób.

Nasz program jest podzielony na moduły (oparte na zasadzie pojedynczej odpowiedzialności) z projektem kontraktu i projektem wdrożenia w czasie rzeczywistym, co oznacza, że ​​w dużym stopniu polegamy na interfejsach. Zwykle przesyłamy dane między modułami za pomocą IEnumerable (ponieważ są one niezmienne), ponieważ są one rodzaj-sortowania z.

+1

Czy mógłbyś zamieścić małą, kompilowalną wersję programu, który to powieli? –

+0

Niestety nasza baza kodów jest podobna do 50000+ LOC z 68 projektami, więc stworzenie dobrego przykładu będzie trudne. Zobacz rozwinięte szczegóły w pytaniu. –

Odpowiedz

5

Nie, nie zrobi tego przepisu.

Rozumiem twoją troskę. Strona wzywająca może nie być świadoma, że ​​jej działania wpływają na wyniki zapytania.

Istnieje kilka przypadków, w których naprawdę nie można tego zrobić:

  • Istnieją przykłady, gdzie spowodowałoby to brak pamięci, takie jak z nieskończonych enumerables lub w wyliczający która produkuje nowo obliczony obraz w każdej iteracji. (Mam obydwa). Jeśli używasz Any() lub First() w swoich zapytaniach. Obie wymagają tylko przeczytania pierwszego elementu. Wszystkie inne prace są wykonywane na próżno.
  • Jeśli oczekujesz, że Enumerables będą połączone za pomocą rur/filtrów. Materializacja wyników pośrednich jest tylko dodatkowym kosztem.

Z drugiej strony, w wielu przypadkach bezpieczniej jest zmaterializować zapytanie w tablicy, gdy można sobie wyobrazić, że użycie macierzy będzie miało skutki uboczne, które wpłyną na zapytanie.

Podczas pisania oprogramowania brzmi zachęcająco, jeśli masz reguły, które mówią: "Kiedy musisz wybrać pomiędzy X i Y, zawsze rób X". Nie wierzę, że istnieją takie zasady. Może w 15% naprawdę powinieneś zrobić X, w 5% zdecydowanie musisz zrobić Y, a w pozostałych przypadkach po prostu nie ma to znaczenia.

Dla pozostałych 80% nic nie pomoże. Jeśli wstawisz wszędzie kod ToArray(), błędnie sugeruje, że był powód, dla którego to zrobiono.

+0

+1, ponieważ "kod błędnie sugeruje, że był ku temu powód".Kiedy zostanie napisany wiersz kodu, nikt nigdy nie będzie bezpieczny, aby go usunąć. Często powoduje to, że kod spaghetti pojawia się w miarę upływu czasu. – Johnny5

2

Jeśli zamierzasz zwrócić IEnumerable (lub IQueryable, lub coś podobnego do tych, które nie jest autonomiczne), ograniczenia, kiedy można je nazwać, co można z nim zrobić i jak długo to może być być trzymane w należytej formie.

Z tych powodów polecam zwrócić FuzzBuzz[] zamiast IEnumerable<FuzzBuzz>, jeśli jest to rodzaj API (np. Między warstwami). Jeśli jest to część wewnętrznej implementacji klasy/modułu, łatwiej jest uzasadnić oszacowanie opóźnienia IEnumerable<FuzzBuzz>, ale nadal rozsądne jest użycie tablicy.

O ile liczba wyników nie jest duża lub jest to często wywoływane, jest mało prawdopodobne, aby była problemem z wydajnością (w wielu sytuacjach czas procesora jest tani, a pamięć przydzielona do macierzy nie zostanie utrzymana zbyt długo).

+0

Należy wspomnieć, że w niektórych sytuacjach (np. Implementacja Linq NHibernate), niejawnie rzutowanie IQueryable na IElumerable spowoduje uruchomienie zapytania. – rossisdead

+0

Podoba mi się wzmianka, że ​​typ zwrotu powinien być decyzją API. Myślę, że jest nas zbyt wielu, którzy nie myślą o API, które wyrządzamy naszym kolegom. –

5

W ogóle nie zawsze wezwanie .ToArray lub .ToList powinien po powrocie wynik LINQ kwerendy.

Zarówno .ToArray, jak i .ToList są "zachłanne" (w przeciwieństwie do leniwych) operacje, które faktycznie wykonują zapytania do źródła danych. Odpowiednie miejsce i czas na ich telefonowanie to decyzja o architekturze. Na przykład możesz utworzyć regułę w swoim projekcie, aby zmaterializować wszystkie zapytania linq wewnątrz warstwy dostępu do danych, a tym samym obsłużyć wyjątek warstwy danych. Lub, aby nie były wykonywane tak długo, jak to jest możliwe, a jedynie uzyskać wymagane dane na samym końcu. I jest wiele innych szczegółów związanych z tym tematem.

Ale zadzwoń lub nie zadzwoń pod numer .ToArray po zwrocie wyników z Twojej funkcji - nie jest to pytanie i nie ma odpowiedzi, dopóki nie przedstawisz bardziej szczegółowej próbki.

+0

Dodałem więcej szczegółów do pytania (chociaż nie jest to szczegółowy przykładowy kod), jeśli chciałbyś zaktualizować swoją odpowiedź ... –

+0

Kluczowym punktem jest nasz program podzielony na moduły (w oparciu o ** zasadę pojedynczej odpowiedzialności * *) _ - czy chciałbyś nie mieszać i jak zamierzasz dzielić _data access_ i _graphową realizację naszego głównego obszaru zainteresowania_? Ta odpowiedź jest indywidualna dla każdego projektu – Akim

2

"Z reguły", nie, nie powinieneś zawsze wywoływać ToList/ToArray. W przeciwnym razie zapytania takie jak myData.GetSomeSubset().WhereOtherCondition().Join(otherdata) spędzają mnóstwo czasu na przydzielaniu tymczasowych buforów dla każdego połączenia łańcuchowego. Ale LINQ działa najlepiej z niezmiennymi kolekcjami. Możesz być bardziej ostrożny w punkcie, w którym modyfikujesz mySecretDataSource.

szczególności, jeśli kod jest zawsze skupia się wokół częstych modyfikacji swojego źródła danych, to brzmi jak dobry powód, aby chętnie zwracają tablicę zamiast IEnumerable

Powiązane problemy