2013-01-01 4 views
14

Rozszerzenia Enumerable.GroupBy i Queryable.GroupBy mają 8 przeciążeń. Dwa z nich (na Enumerable.GroupBy) są:GroupBy z elementemSelector i resultSelector

// (a) 
IEnumerable<TResult> GroupBy<TSource, TKey, TResult>(
    this IEnumerable<TSource> source, 
    Func<TSource, TKey> keySelector, 
    Func<TKey, IEnumerable<TSource>, TResult> resultSelector); 

// (b) 
IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>(
    this IEnumerable<TSource> source, 
    Func<TSource, TKey> keySelector, 
    Func<TSource, TElement> elementSelector, 
    Func<TKey, IEnumerable<TElement>, TResult> resultSelector); 

(dla Queryable.GroupBy taka sama, tylko z Expression<Func<... zamiast Func<...)

(b) posiada dodatkową elementSelector jako parametr.

Na stronie MSDN to an example for overload (a) i an example for overload (b). Obie prace w tej samej kolekcji przykład źródło:

List<Pet> petsList = new List<Pet> 
{ 
    new Pet { Name="Barley", Age=8.3 }, 
    new Pet { Name="Boots", Age=4.9 }, 
    new Pet { Name="Whiskers", Age=1.5 }, 
    new Pet { Name="Daisy", Age=4.3 } 
}; 

przykład (a) używa tego zapytania:

var query = petsList.GroupBy(
    pet => Math.Floor(pet.Age), // keySelector 
    (age, pets) => new   // resultSelector 
    { 
     Key = age, 
     Count = pets.Count(), 
     Min = pets.Min(pet => pet.Age), 
     Max = pets.Max(pet => pet.Age) 
    }); 

i przykładem (b) zastosowania tego zapytania:

var query = petsList.GroupBy(
    pet => Math.Floor(pet.Age), // keySelector 
    pet => pet.Age,    // elementSelector 
    (baseAge, ages) => new  // resultSelector 
    { 
     Key = baseAge, 
     Count = ages.Count(), 
     Min = ages.Min(), 
     Max = ages.Max() 
    }); 

Wynik obu zapytań jest dokładnie taki sam.

Pytanie 1: Czy istnieje zapytanie, którego nie mogę wyrazić przy użyciu samego resultSelector i gdzie naprawdę potrzebowałbym elementSelector? Czy możliwości dwóch przeciążeń są zawsze ekwiwalentne i jest to tylko kwestia gustu, aby skorzystać z jednego lub drugiego sposobu?

Pytanie 2: Czy istnieje odpowiednik dwóch różnych przeciążeń przy użyciu składni zapytania LINQ?

(jako pytanie niepożądane: Przy stosowaniu Queryable.GroupBy z Entity Framework, będą oba przeciążenia zostać przetłumaczony na dokładnie tym samym SQL?)

Odpowiedz

15

Dla IEnumerable:

petsList.GroupBy(
    pet => Math.Floor(pet.Age), // keySelector 
    (age, pets) => new   // resultSelector 
    { 
     Key = age, 
     Count = pets.Count(), 
     Min = pets.Min(pet => pet.Age), 
     Max = pets.Max(pet => pet.Age) 
    }); 

jest equevalent do:

var query = petsList.GroupBy(
    pet => Math.Floor(pet.Age), // keySelector 
    pet => pet,    // elementSelector 
    (baseAge, ages) => new  // resultSelector 
    { 
     Key = baseAge, 
     Count = ages.Count(), 
     Min = ages.Min(pet => pet.Age), 
     Max = ages.Max(pet => pet.Age) 
    }); 

użycie elementu elementSelector może uprościć wyrażenia w resultSelector (porównaj następny i poprzedni):

var query = petsList.GroupBy(
    pet => Math.Floor(pet.Age), // keySelector 
    pet => pet.Age,    // elementSelector 
    (baseAge, ages) => new  // resultSelector 
    { 
     Key = baseAge, 
     Count = ages.Count(), 
     Min = ages.Min(), //there is no lambda due to element selector 
     Max = ages.Max() ////there is no lambda due to element selector 
    }); 

W wersji IQueryable nie jest to takie proste. Można spojrzeć na źródła tej metody:

public static IQueryable<TResult> GroupBy<TSource, TKey, TElement, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, Expression<Func<TSource, TElement>> elementSelector, Expression<Func<TKey, IEnumerable<TElement>, TResult>> resultSelector) 
     { 
      if (source == null) 
       throw Error.ArgumentNull("source"); 
      if (keySelector == null) 
       throw Error.ArgumentNull("keySelector"); 
      if (elementSelector == null) 
       throw Error.ArgumentNull("elementSelector"); 
      if (resultSelector == null) 
       throw Error.ArgumentNull("resultSelector"); 
      return source.Provider.CreateQuery<TResult>(
       Expression.Call(
        null, 
        ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TElement), typeof(TResult)), 
        new Expression[] { source.Expression, Expression.Quote(keySelector), Expression.Quote(elementSelector), Expression.Quote(resultSelector) } 
        )); 
     } 

public static IQueryable<TResult> GroupBy<TSource, TKey, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector,Expression<Func<TKey, IEnumerable<TSource>, TResult>> resultSelector) 
     { 
      if (source == null) 
       throw Error.ArgumentNull("source"); 
      if (keySelector == null) 
       throw Error.ArgumentNull("keySelector"); 
      if (resultSelector == null) 
       throw Error.ArgumentNull("resultSelector"); 
      return source.Provider.CreateQuery<TResult>( 
       Expression.Call(
        null, 
        ((MethodInfo)MethodBase.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TKey), typeof(TResult)), 
        new Expression[] { source.Expression, Expression.Quote(keySelector), Expression.Quote(resultSelector) } 
        )); 
     } 

Jak widać, że wraca różnych wyrażeń, więc nie jestem pewien, że wynik zapytania SQL będą takie same we wszystkich przypadkach, ale przypuszczać że SQL zapytanie o przeciążenie za pomocą elementu itemSelector + resultSelector nie będzie wolniejsze w porównaniu z przeciążeniem bez elementu elementSelector.

Odpowiedź 1: Nie, ponieważ IEnumerable nie ma zapytania, którego nie można wyrazić przy użyciu samego resultSelector.

Odpowiedź 2. Nie, nie ma odpowiednika dla dwóch różnych przeciążeń przy użyciu składni zapytania LINQ. Metody rozszerzeń mają więcej możliwości w porównaniu ze składnią zapytań LINQ.

Odpowiedź 3 (W przypadku pytania pobocznego): nie można zagwarantować, że kwerendy sql będą takie same dla tego przeciążenia.