为什么我不能在扩展方法中用泛型类型变量替换IEnumerable <T>?

Mat*_*att 2 c# generics extension-methods

我试图使扩展方法更通用以避免冗余(这是一些实际代码的示例,下面的代码只是为了演示问题 - 我有想法使该方法也可用IQueryable<T>).

以下工作正常:

public static class Extensions
{
    public static IEnumerable<T> MySelect1<T, V>(this IEnumerable<T> query, Func<T, V> f)
    {
        // do something, then return IEnumerable<T>
        var result=query.AsEnumerable<T>();
        return result;
    }
    public static IQueryable<T> MySelect1<T, V>(this IQueryable<T> query, Func<T, V> f)
    {
        // do something, then return IQueryable<T>
        var result = query.AsQueryable<T>();
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以在LinqPad中使用它(当与Northwind数据库连接时):

var myQuery=(from x in Customers select x);
myQuery.AsEnumerable().MySelect1(d => d.CustomerID).Dump();
myQuery.AsQueryable().MySelect1(d => d.CustomerID).Dump();
Run Code Online (Sandbox Code Playgroud)

现在我想摆脱MySelect1的重复实现,所以我将它重构为:

public static class Extensions
{
    public static E MySelect2<E, T, V>(this E query, Func<T, V> f)
    where E : System.Linq.IQueryable<T>, System.Collections.Generic.IEnumerable<T>
    {
        return (E)query.Select(f);
    }
}
Run Code Online (Sandbox Code Playgroud)

这也编译,但我不能像上面那样使用MySelect2,请考虑以下内容:

// CS0411 The type arguments for method 'Extensions.MySelect2<E, T, V>(E, Func<T, V>)' 
// cannot be inferred from the usage. Try specifying the type arguments explicitly.
myQuery.AsEnumerable().MySelect2(d => d.CustomerID).Dump(); 
myQuery.AsQueryable().MySelect2(d => d.CustomerID).Dump();
Run Code Online (Sandbox Code Playgroud)

好的,做错误要求对此代码行有效:

myQuery.AsQueryable()
       .MySelect2<IQueryable<Customers>, Customers, String>(d => d.CustomerID).Dump();
Run Code Online (Sandbox Code Playgroud)

但不是那个:

myQuery.AsEnumerable<Customers>()
       .MySelect2<IEnumerable<Customers>, Customers, String>(d => d.CustomerID).Dump();
Run Code Online (Sandbox Code Playgroud)

我来了

CS0311类型'System.Collections.Generic.IEnumerable'不能用作泛型类型或方法'Extensions.MySelect2(E,Func)'中的类型参数'E'.没有从'System.Collections.Generic.IEnumerable'到'System.Linq.IQueryable'的隐式引用转换.

为什么?它怎么能修复?请帮忙.

Jon*_*eet 11

为什么?

正是出于错误消息中所述的原因:您尝试将其IEnumerable<Customers>用作类型参数E,但E具有以下约束:

where E : System.Linq.IQueryable<T>
Run Code Online (Sandbox Code Playgroud)

它怎么能修复?

它不能,假设我明白你想要实现的目标.

您正试图实现的"简化"存在一个根本问题:您实际上并没有在原始MySelect1方法中完全复制.第一次通话AsEnumerable()和第二次通话AsQueryable().你试图用演员替换那些,但那是行不通的.

还有一个问题,即使使用原始方法:您接受Func<T, V> f基于可查询方法的参数,这意味着无论何时调用Select或类似并传入f,您都将调用Enumerable.Select而不是Queryable.Select.要真正IQueryable<>正确使用,你应该接受Expression<Func<T, V>> f.那时,你AsQueryable无论如何都不需要打电话.

根据您是使用LINQ to Objects还是使用不同的LINQ提供程序(例如LINQ to SQL),您的两种方法"应该"采用完全不同的路径,并且不能将其隐藏为纯粹的实现细节而不会发生可能的重大更改无论如何,它都没有你想要的那么有用.

  • @Matt:不,你不是.如果有多个约束,则type参数必须满足*all*. (3认同)