GroupBy с элементом Selector и resultSelector - программирование

GroupBy с элементом Selector и resultSelector

Расширения Enumerable.GroupBy и Queryable.GroupBy имеют 8 перегрузок. Два из них (для Enumerable.GroupBy):

// (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);

(для Queryable.GroupBy то же самое, только с Expression<Func<... вместо Func<...)

(b) имеет дополнительный параметр elementSelector.

В MSDN пример для перегрузки (a) и пример для перегрузки (b). Они оба работают с тем же примером исходной коллекции:

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 }
};

Пример (a) использует этот запрос:

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)
    });

И пример (b) использует этот запрос:

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()
    });

Результат обоих запросов точно такой же.

Вопрос 1: Есть ли какой-либо запрос, который я не могу выразить, используя только resultSelector и где мне действительно понадобится elementSelector? Или возможности двух перегрузок всегда эквивалентны, и это просто вопрос вкуса, чтобы использовать один или другой способ?

Вопрос 2: Есть ли дубликат двух разных перегрузок при использовании синтаксиса запроса LINQ?

(В качестве побочного вопроса: при использовании Queryable.GroupBy с Entity Framework будут ли перегружены обе перегрузки в один и тот же SQL?)

4b9b3361

Ответ 1

Для 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)
    });

равнозначно:

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)
    });

использование elementSelector может упростить выражения в resultSelector (сравнить следующий и предыдущий):

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
    });

В IQueryable это не так просто. Вы можете посмотреть источники этих методов:

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) } 
                    ));
        } 

Как вы можете видеть, они возвращают разные выражения, поэтому я не уверен, что результат SQL-запроса будет таким же во всех случаях, но я полагаю, что SQL-запрос для перегрузки с элементом elementelector + resultSelector будет не медленнее сравнивать с перегрузкой без ElementSelector.

Ответ 1: Нет, для IEnumerable нет запроса, который вы не можете выразить, используя только resultSelector.

Ответ 2. Нет, нет дубликатов двух разных перегрузок при использовании синтаксиса запроса LINQ. Методы расширения имеют больше возможностей по сравнению с синтаксисом запроса LINQ.

Ответ 3 (для бокового вопроса): не гарантируется, что запросы sql будут одинаковыми для этих перегрузок.