任何容器的C#泛型函数

时间:2018-07-06 06:34:34

标签: c# generics containers

我有一个看起来像这样的函数:

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 :(得分:3)

不,在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);
}

答案 1 :(得分:0)

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关键字)中的achievable。但是,当您需要使用反射并且突然失去所有编译时检查和保证时,它确实散发出不良的设计气味:您的代码可能会在运行时工作,也许不会。在这种情况下,这将是一个非常糟糕的主意。

鸭式打字的例子通常是简单的用例,但要点是方法签名仍然必须 match 。在这种情况下,情况甚至更糟:此界面中的各种方法具有相同的名称,并且似乎以相同的方式工作,它们是完全不同的。

比较这两种情况,例如:

  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的二进制表达式,该表达式引用了另外两个表达式(ParameterExpressionConstantExpression)。 此对象本身传递给IQueryable.Average时不执行任何计算

    double GetAverage(IQueryable<int> items)
    {
        return items.Average(i => i * 2);
    }