的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
}
}
R1
和R2
就像两个记录类。在您的版本中,R2
继承R1
- 但这与此问题无关,所以我省略了它。 I<T>
是通用接口,代表IEnumerable<T>
。但是我的版本没有方法!他们也与这个问题无关。
然后我把你的集合类继承系统折叠成了两层。基类C1
实现I<T>
,然后派生类C2
选择问C2
到I<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
会进行隐式转换。
因此,为了将之与你的代码,你有一个“向下转换”从BaseRecord
到DerivedRecord
,你来自的LINQ的Cast
操作实现。好吧,foreach
无论如何都会帮你。在我上面的示例中,C
实际上是object
类型的项目的集合。然而,我可以写:
foreach (string item in new C())
{
Console.WriteLine(item.Length);
}
编译器高兴地插入一个无声铸从object
到string
。这些物品可以是任何东西......唷!
这就是为什么var
的出现很不错 - 总是用var
来声明你的foreach
循环变量,这样编译器就不会插入一个转换。它将使变量成为它在编译时从集合中推断出的最具体类型。
DerivedCollection定义在哪里? – 2010-01-25 16:56:10