【问题标题】:A C# generic function for any container适用于任何容器的 C# 通用函数
【发布时间】:2018-07-06 06:34:34
【问题描述】:

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

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>

这不会编译,会给出诸如“',' expected”之类的基本错误。有什么想法吗?

【问题讨论】:

  • “不起作用”是什么意思?异常还是编译器错误?为什么你甚至想要C&lt;TSource 评估为IEnumerable&lt;TSource&lt;TSource&gt;&gt;?我想你只想要C source。无论如何,您不能将泛型参数限制为另一个。
  • 对不起,这有点模棱两可。它无法编译。

标签: c# generics containers


【解决方案1】:

不,在 C# 中没有办法表达一个类型参数,它本身是某种任意类型,必须是具有特定数量的泛型。 (这就是您尝试使用 C 所做的事情。)

同样值得注意的是,您可能不希望IEnumerable&lt;T&gt; 具有相同的签名,因为您通常使用委托而不是表达式树来进行进程内查询。所以你会有

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

【讨论】:

  • 我在编译我的 IEnumerable 版本时发现了这一点。所以无论如何,我真的不能做我想做的事。
【解决方案2】:

Tl;dr:您总是可以在 IEnumerable&lt;T&gt; 上创建一个廉价的 IQueryable&lt;T&gt; 包装器,这非常好,可能会解决您的问题:

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

长版本是您的问题首先说明了接口存在的原因:告诉编译器,如果两个对象实现IEnumerable,那么这是他们必须实现该接口中的所有方法的契约,并且对这些方法的调用不会在运行时失败。

您要问的是完全忽略接口,这称为鸭子类型,实际上甚至是 C# 中的 achievable 使用反射或 DLR(dynamic 关键字)。但是,当您需要使用反射并且突然失去所有编译时检查和保证时,它确实闻起来很糟糕:您的代码可能会在运行时工作,也可能不会。在这种情况下,这将是一个非常糟糕的主意。

鸭子类型的示例通常提供简单的用例,但关键是方法签名仍然必须匹配。在这种特殊情况下,情况更糟:该接口中的各种方法具有相同的名称并且似乎以相同的方式工作,它们完全不同。

比较这两种情况,例如:

  1. IEnumerable&lt;T&gt; 的情况下,我们调用public static double Average&lt;TSource&gt;(this IEnumerable&lt;TSource&gt; source, Func&lt;TSource, int&gt; selector)。该方法的参数是一个匿名方法,即在编译时生成的类中的一个方法,它接受一个参数并返回乘以 2 的值。IEnumerable.Average 方法实际上调用了这个列表中每个项目的方法

    double GetAverage(IEnumerable<int> items)
    {
        return items.Average(i => i * 2);
    }
    
  2. IQueryable&lt;T&gt; 的情况下,我们调用public static double Average&lt;TSource&gt;(this IQueryable&lt;TSource&gt; source, Expression&lt;Func&lt;TSource,int&gt;&gt; selector)。此方法的参数是一个表达式树,即一个包含我们想要将参数与2 相乘的信息的对象。它实际上包含一个Multiply 类型的二进制表达式,它引用了另外两个表达式(ParameterExpressionConstantExpression)。 当传递给IQueryable.Average时,此对象本身不执行任何计算

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

【讨论】:

    猜你喜欢
    • 2022-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-08
    • 1970-01-01
    • 2021-07-11
    • 1970-01-01
    相关资源
    最近更新 更多