2010-01-25 50 views
3

我目前使用以下基本集合。IEnumerable的实现<T>适用于foreach,但不适用于LINQ

abstract class BaseCollection : Collection<BaseRecord> 
    {  
    } 

我想用下面的通用集合替换它。然而,由于当前实现BaseCollection的派生类的数量,这不是一项简单的任务。

abstract class BaseCollection<TRecord> : Collection<TRecord> where TRecord : BaseRecord,new() 
    { 

    } 

而不是一个大规模的大修我想慢慢推出这个新的集合。

abstract class BaseCollection<TRecord> : BaseCollection,IEnumerable<TRecord> where TRecord : BaseRecord,new() 
    { 
     public new IEnumerator<TRecord> GetEnumerator() 
     { 
     return Items.Cast<TRecord>().GetEnumerator(); 
     } 
    } 

虽然我可以使用foreach枚举集合,但下面的LINQ语句不能编译。这可能吗?我意识到这有点破解,但我不知道如何才能完成这项工作。

class Program 
    { 
     static void Main(string[] args) 
     { 
     DerivedCollection derivedCollection = new DerivedCollection 
     { 
      new DerivedRecord(), 
      new DerivedRecord() 
     }; 
     foreach (var record in derivedCollection) 
     { 
      record.DerivedProperty = ""; 
     } 
     var records = derivedCollection.Where(d => d.DerivedProperty == ""); 
     } 
    } 

这里是上面使用的两个记录。谢谢。

class DerivedRecord : BaseRecord 
    { 
     public string DerivedProperty { get; set; } 
    } 

    abstract class BaseRecord 
    { 
     public string BaseProperty { get; set; } 
    } 

这里是派生的集合。

class DerivedCollection : BaseCollection<DerivedRecord> 
    { 

    } 
+1

DerivedCollection定义在哪里? – 2010-01-25 16:56:10

回答

4

的foreach使用了一些 “神奇” 要弄清楚什么类型的遍历。它不需要收集来支持IEnumerable的任何事情。这可能解释了差异。

问题是编译器找不到Where的泛型类型参数。如果你这样做,它会工作:

IEnumerable<DerivedRecord> ie = derivedCollection; 
ie.Where(d => d.DerivedProperty == ""); 

没有使用强制转换,但可用的选择集已减少到一个。

或者你可以显式指定类型参数:

derivedCollection.Where<DerivedRecord>(d => d.DerivedProperty == ""); 

我想解决你的问题你将不得不继承的IEnumerable<T>另一个版本停止BaseCollection

abstract class BaseCollection 
{ 
    private readonly Collection<BaseRecord> _realCollection = new Collection<BaseRecord>(); 

    public void Add(BaseRecord rec) 
    { 
     _realCollection.Add(rec); 
    } 

    public IEnumerable<BaseRecord> Items 
    { 
     get { return _realCollection; } 
    } 
} 

有我仍然在实例化Collection<T>,但作为一个单独的对象而不是基类。然后将它们转发给它以根据需要模仿它的API。

派生的通用版本需要完全实现IEnumerable<T>

class BaseCollection<TRecord> : BaseCollection, IEnumerable<TRecord> 
            where TRecord : BaseRecord,new() 
{ 
    public IEnumerator<TRecord> GetEnumerator() 
    { 
     return Items.Cast<TRecord>().GetEnumerator(); 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 

随后的示例代码,其余工作正常,没有我最初公布的解决方法,因为它只有一个IEnumerable<T>实施的工作。

更新,更多的细节

下面是同样的情况真的削减版本,打破各个环节与IEnumerable,宣布从头开始自己的一切,所以我们可以看到发生了什么。

public class R1 { } 
public class R2 { } 
public interface I<T> { } 
public class C1<T> : I<T> { } 
public class C2 : C1<R1>, I<R2> { } 

class Program 
{ 
    public static I<T> M<T>(I<T> i) { return i; } 

    static void Main(string[] args) 
    { 
     var c2 = new C2(); 
     var v = M(c2); // Compiler error - no definition for M 
    } 
} 

R1R2就像两个记录类。在您的版本中,R2继承R1 - 但这与此问题无关,所以我省略了它。 I<T>是通用接口,代表IEnumerable<T>。但是我的版本没有方法!他们也与这个问题无关。

然后我把你的集合类继承系统折叠成了两层。基类C1实现I<T>,然后派生类C2选择问C2I<R2>直接实现I<R1>然后工具。层数也没有区别。 where也没有声明类型约束,所以在这里也省略了。

结果是C2有两个实现I<T>I<R1>I<R2>。一个它继承自C1,另一个它自己增加。

最后我有一个方法M,它代表了Linq中的Where。它不需要是一个展示问题的扩展方法,所以为了清晰起见,我将它变成了一个普通的静态方法。

所以当我们来调用我们的方法M时,编译器必须弄清楚什么是T。它通过查看我们已经通过的唯一参数来完成此操作,该参数必须支持I<T>。不幸的是,我们传递了一些支持I<R1>I<R2>的东西,那么类型推断过程如何在它们之间做出选择呢?它不能。

由于我的界面没有方法,所以在方法前面明确插入new不会帮助我,这就是为什么它不能帮助你。问题不在于决定调用接口中的哪种方法,而是要将参数M视为I<R1>I<R2>

为什么编译器不会将其报告为类型推断问题?根据C#3.0规范,它不会 - 首先运行类型推断,产生一组可用的重载,然后重载解析运行以选择最佳选择。如果类型推断无法在泛型方法的两个可能扩展之间做出决定,则它将消除这两者,因此重载解析甚至不会看到它们,因此错误消息表示没有任何可用的方法,称为M。 (但是如果你有Resharper,它有它自己的编译器,它用来在IDE中给出更详细的错误,在这种情况下它具体说:“方法M的类型参数不能从使用中推断出来” )

现在,为什么foreach不同?因为它甚至不安全!它可以追溯到仿制药添加之前。它甚至没有看接口。它只是寻找一个名为GetEnumerator的公共方法,无论它通过哪种类型。例如:

public class C 
{ 
    public IEnumerator GetEnumerator() { return null; } 
} 

说,就因为编译器来说是一个非常好的收藏! (当然,它会在运行时炸掉,因为它返回空值IEnumerator。)但请注意IEnumerable或任何泛型的缺失。这意味着foreach会进行隐式转换。

因此,为了将之与你的代码,你有一个“向下转换”从BaseRecordDerivedRecord,你来自的LINQ的Cast操作实现。好吧,foreach无论如何都会帮你。在我上面的示例中,C实际上是object类型的项目的集合。然而,我可以写:

foreach (string item in new C()) 
{ 
    Console.WriteLine(item.Length); 
} 

编译器高兴地插入一个无声铸从objectstring。这些物品可以是任何东西......唷!

这就是为什么var的出现很不错 - 总是用var来声明你的foreach循环变量,这样编译器就不会插入一个转换。它将使变量成为它在编译时从集合中推断出的最具体类型。

+0

如果这是一个类型推断问题,我会期待像“用于......的类型参数不能从用法推断”的错误。相反,我得到以下错误... 'ConsoleApplication1.DerivedCollection'不包含'Where'的定义并且没有扩展方法'Where'接受类型为'ConsoleApplication1.DerivedCollection'的第一个参数可以被发现 – barry 2010-01-25 17:34:40

+0

我同意这个是问题......“你将不得不停止BaseCollection继承另一个版本的IEnumerable ”。 我猜隐藏BaseCollection的IEnumerable 不适用于LINQ? – barry 2010-01-25 17:40:21

+0

上面更多的解释... – 2010-01-26 10:46:17

0

即使丑陋,但工作原理:

(IEnumerable<DerivedRecord>)derivedCollection).Where(d => d.DerivedProperty == "") 
0

运算有可能已经解决了这个,但如果它可以帮助其他人,您现有的LINQ当您尝试使用BaseCollection因为它有两个IEnumerable的类型的中断:IEnumerable<TRecord>IEnumerable<BaseRecord>(从BaseCollection继承 - >Collection<BaseRecord>)。要让现有的Linq在具有两种IEnumerable类型的情况下进行编译,请通过实现IQueryable<TypeYouWantToUse>来定义您希望Linq使用哪种IEnumerable。

abstract class BaseCollection<TRecord> : BaseCollection, IEnumerable<TRecord>, IQueryable<TRecord> where TRecord : BaseRecord, new() 
{ 
    public new IEnumerator<TRecord> GetEnumerator() 
    { 
     return Items.Cast<TRecord>().GetEnumerator(); 
    } 

    #region IQueryable<TRecord> Implementation 
    public Type ElementType 
    { 
     get { return typeof(TRecord); } 
    } 

    public System.Linq.Expressions.Expression Expression 
    { 
     get { return this.ToList<TRecord>().AsQueryable().Expression; } 
    } 

    public IQueryProvider Provider 
    { 
     get { return this.ToList<TRecord>().AsQueryable().Provider; } 
    } 
    #endregion 
} 
相关问题