2010-11-12 20 views
5

Próbuję zawrzeć w głowie to, co robi kompilator C#, gdy używam metod linq, szczególnie w przypadku wielokrotnego łączenia tej samej metody.Zrozumienie, w jaki sposób kompilator C# radzi sobie z metodami łańcuchowymi linq

Prosty przykład: Załóżmy, że próbuję filtrować sekwencję zmiennych na podstawie dwóch warunków.

Najbardziej oczywistą rzeczą do zrobienia jest coś takiego:

IEnumerable<int> Method1(IEnumerable<int> input) 
{ 
    return input.Where(i => i % 3 == 0 && i % 5 == 0); 
} 

Ale mógłby również łańcuch metod, gdzie z jednego stanu w każdy:

IEnumerable<int> Method2(IEnumerable<int> input) 
{ 
    return input.Where(i => i % 3 == 0).Where(i => i % 5 == 0); 
} 

miałem spójrz na IL w Reflectorze; to jest oczywiście różna dla obu metod, ale analizując go dalej jest poza moją wiedzą w tej chwili :)

chciałbym dowiedzieć się:
a) co kompilator robi się inaczej w każdym przypadku, i dlaczego.
b) istnieją jakiekolwiek implikacje wydajności (nie próbując mikro-optimize!; Po prostu ciekawy)

Odpowiedz

9

Odpowiedź na (a) jest krótki, ale pójdę do bardziej szczegółowo poniżej:

Kompilator nie faktycznie zrobić łańcuchowym - zdarza się w czasie wykonywania, w ramach zwykłej organizacja obiektów! Tutaj jest znacznie mniej magii niż na pierwszy rzut oka - Jon Skeet recently completed the "Where clause" step w swoim blogu "Re-implementation LINQ to Objects". Zaleciłbym przeczytanie tego.

w bardzo krótkich terminach, co się dzieje, jest taka: za każdym razem wywołać metodę rozszerzenia Where, zwraca nowy WhereEnumerable obiekt, który ma dwie rzeczy - odniesienie do poprzedniego IEnumerable (tej, którą nazwie Where dalej), a lambda, którą podałeś.

Po uruchomieniu iteracji nad tym WhereEnumerable (na przykład w foreach później w dół w kodzie), wewnętrznie po prostu zaczyna iteracji na IEnumerable że to się odwoływać.

„To foreach właśnie poprosił mnie do następnego elementu w mojej sekwencji, więc ja obracam się wokół i prośbą do następnego elementu w swojej sekwencji”.

To idzie całą drogę w dół łańcucha, dopóki nie trafimy na pochodzenie, które w rzeczywistości jest rodzajem tablicy lub przechowywania prawdziwych elementów.Gdy każdy z obiektów Enumerable mówi "OK, oto mój element" przekazuje go z powrotem do łańcucha, stosuje również własną niestandardową logikę. W przypadku Where stosuje lambda, aby sprawdzić, czy element spełnia kryteria. Jeśli tak, zezwala na kontynuowanie połączenia z następnym rozmówcą. Jeśli się nie powiedzie, zatrzyma się w tym momencie, wróci do swojej referencyjnej zmiennej i poprosi o następny element.

Tak się dzieje, dopóki wszyscy nie zwrócą wartości MoveNext, co oznacza, że ​​wyliczenie jest zakończone i nie ma już żadnych elementów.

Aby odpowiedzieć (b), tam zawsze różnica, ale tutaj jest to zbyt trywialne by przejmować. Nie martw się o to :)

+0

Dobra odpowiedź. Potrzebujemy więcej takich materiałów na Stackoverflow. –

1
  1. pierwszej użyje jednego iterator, drugi użyje dwa. Oznacza to, że pierwszy tworzy rurociąg z jednym etapem, a drugi obejmuje dwa etapy.

  2. Dwa iteratory mają niewielką wadę wydajnościową względem jednej.