当模糊地使用IEnumerable时,Linq函数会给出奇怪的编译错误 - 可能的解决方法?

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函数,即使没有接口).第四种情况下的错误信息然后移动到它试图制作动物的线(这恰好是一只狗,但类型系统不知道是狗)树皮.这可以通过改变varDogforeach循环来修复(并且为了完整性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(...)预期的编译,即使它是不使用不同类型参数编译的同一个类!

Jon*_*eet 9

如你所说,错误信息是不幸的,因为问题是模棱两可而不是Where根本没有找到(假设你有一个using指令System.Linq).问题是编译器无法推断出类型参数Enumerable.Where,因为AnimalGroup<Dog>实现了IEnumerable<Animal>IEnumerable<Dog>.

不涉及更改的选项AnimalGroup:

您可以使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-在其他情况下更难)或无法实现这两个接口.

  • @Steve:嗯.这很奇怪,因为那时`AnimalGroup <Dog>`将不会是你通常所期望的`AnimalGroup`.如果我在代码库中看到它,我肯定会发现这是一个非常不寻常的解决方案......您可能想要考虑未来的用例以及它们是否会导致问题.当然,如果他们不这样做,那很好...... (2认同)

pku*_*rov 3

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)