Mat*_*oni 0 c# linq ienumerable
在我正在使用的库中,遇到调用IEnumerable之外的任何LINQ方法的问题。我有一个如下的类层次结构(命名有点奇怪,因为它被内部代码所混淆了)
Item : GeneralObject
ItemCollection : GenericCollection<ItemCollection, Item>
GenericCollection<TCollection, TItem> : GeneralObjectCollection, IEnumerable<TItem>
where TCollection : GenericCollection<TCollection, TItem>
where TItem : GeneralObject
GeneralObjectCollection : ICollection, IEnumerable<GeneralObject>
Run Code Online (Sandbox Code Playgroud)
可以看出,IEnumerable在ItemCollection的类层次结构中有两次,因此有两种GetEnumerator方法,一种提供a GeneralObject,一种提供a Item。
当我查看VS2019中通过元数据提供的类定义时,实现的每个类IEnumerable<TItem>也显示为Implemented IEnumerable。
With this setup, I am unable to call most LINQ methods like Select, Where, etc, on any ItemCollection instances, and can only do those on IEnumerable. It looks like it should clearly support methods on IEnumerable<T> as well, but for some reason I have to cast it first.
Both casting to IEnumerable<TItem> and using .Cast<TItem>, work but these seem like they should be unnecessary.
Code sample below. The second example would not compile:
private ItemCollection GetItemsFromDatabase(string query)
{
// Internal logic.
}
List<Item> newItemList = ((IEnumerable<Item>)GetItemsFromDatabase(itemQuery))
.Select(x => new ItemInfo(x.Name, x.Id, x.Guid)).ToList();
Run Code Online (Sandbox Code Playgroud)
and
private ItemCollection GetItemsFromDatabase(string query)
{
// Internal logic.
}
List<Item> newItemList = GetItemsFromDatabase(itemQuery).Select(x => new ItemInfo(x.Name, x.Id, x.Guid)).ToList();
Run Code Online (Sandbox Code Playgroud)
The error is: 'ItemCollection' does not contain a definition for 'Select' and no accessible extension method 'Select' accepting a first argument of type 'ItemCollection' could be found (are you missing a using directive or an assembly reference?).
I am using LINQ elsewhere in this file with no issue, so it is not a matter of providing the right using or an actual assembly reference.
当您正确猜想时,就会出现问题,因为在类型推断期间发现了歧义。当你说:
class Ark : IEnumerable<Turtle>, IEnumerable<Giraffe>
{ ... }
Run Code Online (Sandbox Code Playgroud)
然后你说
Ark ark = whatever;
ark.Select(x => whatever);
Run Code Online (Sandbox Code Playgroud)
编译器必须以某种方式知道您的意思a.Select<Turtle, Result>还是a.Select<Giraffe, Result>。在任何情况下C#都不会试图猜测您的意思,a.Select<Animal, Result>或者a.Select<object, Result>因为那不是所提供的选择之一。C#仅从可用类型中进行选择,并且可用类型为IEnumerable<Turtle>和IEnumerable<Giraffe>。
如果没有决策依据,则类型推断将失败。由于我们仅在所有其他重载解析尝试均失败后才使用扩展方法,因此,此时我们可能会失败重载解析。
有很多方法可以使这项工作起作用,但是所有这些方法都以某种方式为C#提供了有关您的含义的提示。最简单的方法是
ark.Select<Turtle, Result>(x => whatever);
Run Code Online (Sandbox Code Playgroud)
但是你也可以
ark.Select((Turtle x) => whatever);
Run Code Online (Sandbox Code Playgroud)
要么
((IEnumerable<Turtle>)ark).Select(x => whatever);
Run Code Online (Sandbox Code Playgroud)
要么
(ark as IEnumerable<Turtle>).Select(x => whatever);
Run Code Online (Sandbox Code Playgroud)
这些都很好。您推论这编译:
ark.Cast<Turtle>().Select(x => whatever);
// NEVER DO THIS IN THIS SCENARIO
// USE ANY OF THE OTHER TECHNIQUES, NEVER THIS ONE
Run Code Online (Sandbox Code Playgroud)
您知道为什么会有危险吗?在您了解为什么这可能是错误的之前,请不要继续。通过推理。
通常,实现一个实现两个“相同”通用接口的类型是一种危险的做法,因为可能会发生非常奇怪的事情。语言和运行时并不是为了优雅地处理这种统一而设计的。例如,考虑协方差是如何工作的;如果我们投降ark会IEnumerable<Animal>怎样?看看是否可以解决;然后尝试一下,看你是否正确。
不幸的是,你的处境更糟。如果实例什么GenericCollection<TCollection, TItem>这样TItem的GeneralObject?现在您已经执行了IEnumerable<GeneralObject>两次!这确实使用户感到困惑,CLR根本不喜欢这样。
更好的做法是使Ark实现既不实现接口,又公开两种方法,一种返回海龟,另一种返回长颈鹿。您应该强烈考虑在课堂上做同样的事情。更好的设计是GenericCollection<TCollection, TItem>不执行IEnumerable<TItem>,而是拥有属性IEnumerable<TItem> Items { get { ... } }。
通过这种设计,您便可以collection.Select获取通用对象或collection.Items.Select项目,然后问题就消失了。
| 归档时间: |
|
| 查看次数: |
254 次 |
| 最近记录: |