给定的代码类似于以下内容(具有实际用例中的实现):
class Animal
{
public bool IsHungry { get; }
public void Feed() { }
}
class Dog : Animal
{
public void Bark() { }
}
class AnimalGroup : IEnumerable<Animal>
{
public IEnumerator<Animal> GetEnumerator() { throw new NotImplementedException(); }
IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); }
}
class AnimalGroup<T> : AnimalGroup, IEnumerable<T>
where T : Animal
{
public new IEnumerator<T> GetEnumerator() { throw new NotImplementedException(); }
}
用简单的foreach进行一切工作都很好...例如以下编译正常:
var animals = new AnimalGroup();
var dogs = new AnimalGroup<Dog>();
// feed all the animals
foreach (var animal in animals)
animal.Feed();
// make all the dogs bark
foreach (var dog in dogs)
dog.Bark();
我们还可以编译代码来喂养所有饥饿的动物:
// feed all the hungry animals
foreach (var animal in animals.Where(a => a.IsHungry))
animal.Feed();
...但是如果我们尝试使用类似的代码更改来仅使饥饿的狗吠叫,则会出现编译错误
// make all the hungry dogs bark
foreach (var dog in dogs.Where(d => d.IsHungry))
dog.Bark();
// error CS1061: 'AnimalGroup<Dog>' does not contain a definition for 'Where' and
// no extension method 'Where' accepting a first argument of type 'AnimalGroup<Dog>'
// could be found (are you missing a using directive or an assembly reference?)
这似乎是一个非常奇怪的错误,因为实际上有可以使用的扩展方法。我认为这是因为编译器认为它对于Where需要使用哪个泛型参数是模棱两可的,并且对于模棱两可的泛型参数到最佳匹配扩展函数的情况没有足够明确的错误消息。
如果相反,我将在没有接口(interface)的情况下定义
AnimalGroup<T>
:class AnimalGroup<T> : AnimalGroup
where T : Animal
{
public new IEnumerator<T> GetEnumerator() { throw new NotImplementedException(); }
}
前三个测试用例仍然有效(因为即使没有接口(interface),foreach也会使用GetEnumerator函数)。然后,第4种情况下的错误消息将移至它试图制作动物(碰巧是狗,但类型系统不知道是狗)树皮的行。可以通过在foreach循环中将
var
更改为Dog
来解决此问题(并且为了确保完整性,使用dog?.Bark()
以防万一枚举器返回了任何非狗)。在我的实际用例中,我更想处理
AnimalGroup<T>
而不是AnimalGroup
(它实际上是使用IReadOnlyList<T>
而不是IEnumerable<T>
)。与使情况2和4像预期的那样工作的代码相比,允许直接在AnimalGroup
上直接调用Linq函数具有更高的优先级(对于完整性也很理想,但优先级要低得多),因此我通过不带接口(interface)的重新定义AnimalGroup
来处理它这:class AnimalGroup
{
public IEnumerator<Animal> GetEnumerator() { throw new NotImplementedException(); }
}
class AnimalGroup<T> : AnimalGroup, IEnumerable<T>
where T : Animal
{
public new IEnumerator<T> GetEnumerator() { throw new NotImplementedException(); }
IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); }
}
这将错误转移到第三种情况下:“喂饱所有饥饿的动物”(错误消息在这种情况下很有意义-确实没有适用的扩展方法),我现在可以接受。 (现在我想到了,我可以在基类上留下一个非通用的
IEnumerable
接口(interface),但这没有任何好处,因为Linq函数仅在通用接口(interface)上运行,foreach不需要它,并且使用IEnumerable的调用者会必须从object
强制转换结果)。有没有我还没有想到的方法,例如,我可以重新定义
AnimalGroup
和/或AnimalGroup<T>
,以便所有这4个测试用例都能按预期进行编译,而后2个直接调用Enumerable.Where
(而不是我定义的其他一些Where
函数) )?有趣的边界情况也是
var genericAnimals = new AnimalGroup<Animal>;
。像genericAnimals.Where(...)
这样的用法可以按预期编译,即使它是同一类,但没有使用不同的类型参数进行编译! 最佳答案
如何从通用AnimalGroup
实现非通用AnimalGroup<T>
:
class AnimalGroup<T> : IEnumerable<T>
where T : Animal
{
public IEnumerator<T> GetEnumerator() { throw new NotImplementedException(); }
IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); }
}
class AnimalGroup : AnimalGroup<Animal> { }