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

标签 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(); }
}

一切都适用于普通的 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 是不明确的,并且对于最匹配扩展函数的不明确通用参数的情况没有足够具体的错误消息。

如果相反,我要定义 AnimalGroup<T>没有界面:

class AnimalGroup<T> : AnimalGroup
  where T : Animal
{
  public new IEnumerator<T> GetEnumerator() { throw new NotImplementedException(); }
}

前 3 个测试用例仍然有效(因为即使没有接口(interface),foreach 也使用 GetEnumerator 函数)。第 4 种情况的错误消息然后移动到它试图让动物(恰好是狗,但类型系统不知道它是狗)吠叫的那一行。这可以通过更改 var 来解决。至 Dog在 foreach 循环中(为了完整性,使用 dog?.Bark() 以防从枚举器返回任何非狗)。

在我的实际用例中,我更有可能想要处理 AnimalGroup<T>AnimalGroup (它实际上使用的是 IReadOnlyList<T> 而不是 IEnumerable<T> )。使案例 2 和 4 之类的代码按预期工作比允许在 AnimalGroup 上直接调用 Linq 函数具有更高的优先级(对于完整性也是可取的,但优先级要低得多),所以我通过重新定义 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(...) 一样使用按预期编译,即使它是没有使用不同类型参数编译的同一个类!

最佳答案

正如您所说,错误消息很不幸,因为问题是歧义而不是 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))
    

你可以制作AnimalGroup实现非泛型 IEnumerable - 这不会混淆编译器,因为没有 Where非通用接口(interface)的方法。然后您仍然可以使用 Cast 实现您的第三个用例:

 foreach (var animal in animals.Cast<Animal>().Where(a => a.IsHungry))

从根本上说,当一个类型实现了 IEnumerable<Foo>IEnumerable<Bar>将遇到类型推断问题 - 因此您需要在不使用类型推断(对于 Where 很容易 - 在其他情况下更难)或不实现这两个接口(interface)之间做出选择。

关于c# - 当模棱两可地使用 IEnumerable 时,Linq 函数会给出奇怪的编译错误 - 可能的解决方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45210075/

相关文章:

.net - Linq to SQL vs Entity Framework,微软支持

Java - ArrayList - 访问对象字段

java - 无法将 ArrayList<Impl> 传递给采用 ArrayList<Interface> 的方法

c# - 如何刷新下拉列表值更改的页面?

c# - 引用组合框中的值时 LINQ 查询出错

c# - 自动 .ToString()?

.net - 测试IEnumerable <T>是否包含1个元素而不进行计数或不使用Single

java - Orika 通用集合自定义映射

c# - 是否可以使用 C# 检测 DTMF 音调

c# - 在.Net中 stub /模拟数据库