Универсальная функция C # для любого контейнера - PullRequest
0 голосов
/ 06 июля 2018

У меня есть функция, которая выглядит так:

static public IQueryable<TSource> OrderData<TSource, TKey>(this IQueryable<TSource> source,
    System.Linq.Expressions.Expression<Func<TSource, TKey>> keySelector,
    Sort.SortDirection sortDirection)
{
    if (sortDirection == Sort.SortDirection.Ascending)
    {
        return source.OrderBy<TSource, TKey>(keySelector);
    }
    else
    {
        return source.OrderByDescending<TSource, TKey>(keySelector);
    }
}

Теперь это было здорово, пока мне не нужно было сделать то же самое для контейнера IEnumerable. Я мог бы назвать это, бросая контейнер на входе и выходе, но мне было интересно, есть ли способ сделать сам контейнер универсальным параметром, и при этом он все еще работает.

Я хотел что-то вроде:

static public C<TSource> OrderData<C, TSource, TKey>(this C<TSource> source,
    System.Linq.Expressions.Expression<Func<TSource, TKey>> keySelector,
    Sort.SortDirection sortDirection) where C : IEnumerable<TSource>

Это не компилируется, выдавая фундаментальные ошибки типа "',' ожидаемый". Есть идеи?

Ответы [ 2 ]

0 голосов
/ 06 июля 2018

Tl; dr: вы всегда можете создать дешевую оболочку IQueryable<T> поверх IEnumerable<T>, которая прекрасно подойдет и, вероятно, решит вашу проблему:

IEnumerable<int> nums_enumerable = new[] { 1, 2, 3 }.AsEnumerable();
IQueryable<int> nums_queryable = nums_enumerable.AsQueryable();

Длинная версия состоит в том, что ваша проблема показывает, почему интерфейсы существуют в первую очередь: сообщить компилятору, что, если два объекта реализуют IEnumerable, то это контракт, по которому они должны реализовать все методы в этом интерфейсе и вызов этих методов не будет завершен во время выполнения.

То, что вы спрашиваете, - это полное игнорирование интерфейса, которое называется типизацией утки и фактически даже достижимо в C # с использованием отражения или DLR (ключевое слово dynamic). Однако это действительно пахнет плохим дизайном, когда вам нужно использовать отражение, и вы внезапно теряете все проверки и гарантии во время компиляции: ваш код может работать во время выполнения, а может и нет. В этом случае это была бы очень плохая идея.

Примеры типизации утки обычно представляют собой простые варианты использования, но дело в том, что сигнатура метода все еще должна соответствовать . В данном конкретном случае это еще хуже: различные методы в этом интерфейсе имеют одно и то же имя, и кажется, работают одинаково, они совершенно разные.

Сравните эти два случая, например:

  1. В случае IEnumerable<T> мы звоним public static double Average<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector). Параметром этого метода является анонимный метод , т. Е. Метод в сгенерированном во время компиляции классе, который принимает параметр и возвращает значение, умноженное на 2. Метод IEnumerable.Average фактически вызывает этот метод для каждого элемента в списке:

    double GetAverage(IEnumerable<int> items)
    {
        return items.Average(i => i * 2);
    }
    
  2. В случае IQueryable<T> мы звоним public static double Average<TSource>(this IQueryable<TSource> source, Expression<Func<TSource,int>> selector). Параметром этого метода является дерево выражений , то есть объект, содержащий информацию, которую мы хотим умножить на параметр 2. Он буквально содержит двоичное выражение типа Multiply, которое ссылается на два других выражения (a ParameterExpression и a ConstantExpression). Этот объект сам не выполняет никаких вычислений при передаче в IQueryable.Average:

    double GetAverage(IQueryable<int> items)
    {
        return items.Average(i => i * 2);
    }
    
0 голосов
/ 06 июля 2018

Нет, в C # нет способа выразить параметр типа, который сам по себе является произвольным типом, который должен быть универсальным с определенной арностью. (Это то, что вы пытаетесь сделать с C.)

Стоит также отметить, что вы, вероятно, не хотите иметь одинаковую подпись для IEnumerable<T>, так как вы обычно работаете с делегатами, а не с деревьями выражений для внутрипроцессных запросов. Таким образом, у вас есть

public static IEnumerable<TSource> OrderData<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Sort.SortDirection sortDirection)
{
    return sortDirection == Sort.SortDirection.Ascending
        ? source.OrderBy(keySelector)
        : source.OrderByDescending(keySelector);
}
...