Ste*_*eve 10 c# linq generics ienumerable
给定类似于以下的代码(在实际用例中实现):
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(); }
}
Run Code Online (Sandbox Code Playgroud)
一切都很好用一个普通的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();
Run Code Online (Sandbox Code Playgroud)
我们还可以编译代码来喂养所有饥饿的动物:
// feed all the hungry animals
foreach (var animal in animals.Where(a => a.IsHungry))
animal.Feed();
Run Code Online (Sandbox Code Playgroud)
...但是如果我们尝试使用类似的代码更改来使只有饥饿的狗吠,我们会收到编译错误
// 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?)
Run Code Online (Sandbox Code Playgroud)
这似乎是一个非常奇怪的错误,因为事实上有一种可以使用的扩展方法.我假设这是因为编译器认为它需要用于Where的哪个泛型参数是模糊的,并且对于最佳匹配扩展函数的模糊泛型参数的情况没有足够的特定错误消息.
相反,我是在AnimalGroup<T>没有接口的情况下定义的:
class AnimalGroup<T> : AnimalGroup
where T : Animal
{
public new IEnumerator<T> GetEnumerator() { throw new NotImplementedException(); }
}
Run Code Online (Sandbox Code Playgroud)
前3个测试用例仍然有效(因为foreach使用GetEnumerator函数,即使没有接口).第四种情况下的错误信息然后移动到它试图制作动物的线(这恰好是一只狗,但类型系统不知道是狗)树皮.这可以通过改变var到Dogforeach循环来修复(并且为了完整性dog?.Bark(),以防万一从枚举器返回任何非狗).
在我的实际使用情况下,我更容易被想对付AnimalGroup<T>比AnimalGroup(和它的实际使用IReadOnlyList<T>,而不是IEnumerable<T>).使像2和4这样的代码按预期工作的优先级远高于允许直接调用Linq函数AnimalGroup(也是完整性,但优先级低得多)的优先级,所以我通过重新定义来处理它,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(); }
}
Run Code Online (Sandbox Code Playgroud)
这会将错误移至第三种情况"为所有饥饿的动物提供食物"(并且错误信息在该上下文中是有意义的 - 实际上没有适用的扩展方法),我现在可以使用它.(现在我想到了,我可以IEnumerable在基类上留下一个非泛型接口,但这没有任何好处,因为Linq函数只在通用接口上运行,foreach不需要它,并且使用IEnumerable的调用者会有从中投出结果object).
有什么方法我还没有想到,这样我可以重新定义AnimalGroup和/或AnimalGroup<T>所有这四个测试用例按预期编译,后者直接调用Enumerable.Where(而不是Where我定义的其他函数)?
一个有趣的边界案例也是var genericAnimals = new AnimalGroup<Animal>;.使用像genericAnimals.Where(...)预期的编译,即使它是不使用不同类型参数编译的同一个类!
如你所说,错误信息是不幸的,因为问题是模棱两可而不是Where根本没有找到(假设你有一个using指令System.Linq).问题是编译器无法推断出类型参数Enumerable.Where,因为AnimalGroup<Dog>实现了IEnumerable<Animal>和IEnumerable<Dog>.
不涉及更改的选项AnimalGroup:
dogs的IEnumerable<Dog>,而不是AnimalGroup<Dog>直接指定类型参数:
foreach (var dog in dogs.Where<Dog>(d => d.IsHungry))
Run Code Online (Sandbox Code Playgroud)您可以使AnimalGroup实现非泛型IEnumerable- 这不会混淆编译器,因为没有Where非泛型接口的方法.然后,您仍然可以使用Cast以下方法实现第三个用例:
foreach (var animal in animals.Cast<Animal>().Where(a => a.IsHungry))
Run Code Online (Sandbox Code Playgroud)
从根本上说,当式工具IEnumerable<Foo>和IEnumerable<Bar>你是要和类型推断的问题-所以你需要不使用类型推断(方便之间做出选择Where-在其他情况下更难)或无法实现这两个接口.
AnimalGroup如何从 generic实现非generic 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> { }
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
211 次 |
| 最近记录: |