只能在IEnumerable上调用Cast和OfType [英] Only able to call Cast and OfType on IEnumerable

查看:80
本文介绍了只能在IEnumerable上调用Cast和OfType的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我正在使用的库中,我遇到了调用IEnumerable之外的任何LINQ方法的问题.我有一个如下的类层次结构(命名有点奇怪,因为它是从内部代码中混淆的)

In a library I am using I have am having issues calling any LINQ methods other than those on IEnumerable. I have a class hierarchy as follows (naming is slightly strange, since it is obfuscated from internal code)

Item : GeneralObject

ItemCollection : GenericCollection<ItemCollection, Item>

GenericCollection<TCollection, TItem> : GeneralObjectCollection, IEnumerable<TItem>
    where TCollection : GenericCollection<TCollection, TItem>
    where TItem : GeneralObject

GeneralObjectCollection : ICollection, IEnumerable<GeneralObject>

可以看出,IEnumerable在ItemCollection的类层次结构中两次,因此有两种GetEnumerator方法,一种提供GeneralObject,一种提供Item.

As can be seen, IEnumerable is in ItemCollection's class hierarchy twice, so there are two GetEnumerator methods, one which provides a GeneralObject and one which provides an Item.

当我查看VS2019中通过元数据提供的类定义时,每个实现IEnumerable<TItem>的类也显示为已实现IEnumerable.

When I look at the class definition provided via metadata in VS2019, each class that implements IEnumerable<TItem> also shows as implemented IEnumerable.

使用此设置,我无法在任何ItemCollection实例上调用大多数LINQ方法,例如Select,Where等,并且只能在IEnumerable上执行.看起来它显然也应该支持IEnumerable<T>上的方法,但是出于某些原因,我必须先将其强制转换.

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.

强制转换为IEnumerable<TItem>并使用.Cast<TItem>都可以,但是这些似乎应该没有必要.

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();

private ItemCollection GetItemsFromDatabase(string query)
{
    // Internal logic.
}

List<Item> newItemList = GetItemsFromDatabase(itemQuery).Select(x => new ItemInfo(x.Name, x.Id, x.Guid)).ToList();

错误是: "ItemCollection"不包含"Select"的定义,并且找不到包含第一个类型为"ItemCollection"的自变量的可访问扩展方法"Select"(是否缺少using指令或程序集引用?).

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?).

我正在该文件中的其他地方正常使用LINQ,因此,提供正确的使用或实际的装配引用不是问题.

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.

推荐答案

正确地猜想,问题是由于在类型推断过程中发现的歧义而引起的.当你说:

As you correctly conjectured, the problem arises because of ambiguities discovered during type inference. When you say:

class Ark : IEnumerable<Turtle>, IEnumerable<Giraffe> 
{ ... }

然后你说

Ark ark = whatever;
ark.Select(x => whatever);

编译器必须以某种方式知道您的意思是a.Select<Turtle, Result>还是a.Select<Giraffe, Result>.在任何情况下,C#都不会猜测您的意思是a.Select<Animal, Result>a.Select<object, Result>,因为这不是所提供的选择之一. C#仅从可用类型中进行选择,可用类型为IEnumerable<Turtle>IEnumerable<Giraffe>.

somehow the compiler has to know whether you meant a.Select<Turtle, Result> or a.Select<Giraffe, Result>. Under no circumstances will C# try to guess that you meant a.Select<Animal, Result> or a.Select<object, Result> because that was not one of the choices provided. C# only makes choices from available types, and the available types are IEnumerable<Turtle> and IEnumerable<Giraffe>.

如果没有依据可做出决定,则类型推断失败.由于我们只有在所有其他重载解析尝试都失败之后才使用扩展方法,因此我们很可能会在此时使重载解析失败.

If there is no basis upon which to make a decision then type inference fails. Since we only got to extension methods after all other overload resolution attempts failed, we're probably going to fail overload resolution at this point.

有很多方法可以使这项工作起作用,但是所有这些方法都以某种方式为C#提供了有关您的含义的提示.最简单的方法是

There are many ways to make this work, but all of them involve somehow giving C# a hint about what you meant. The easiest way is

ark.Select<Turtle, Result>(x => whatever);

但是你也可以做

ark.Select((Turtle x) => whatever);

((IEnumerable<Turtle>)ark).Select(x => whatever);

(ark as IEnumerable<Turtle>).Select(x => whatever);

这些都很好.您推断此代码可以编译:

Those are all good. You deduced that this compiles:

ark.Cast<Turtle>().Select(x => whatever); 
// NEVER DO THIS IN THIS SCENARIO
// USE ANY OF THE OTHER TECHNIQUES, NEVER THIS ONE

您知道它为什么很危险吗?在您了解为什么这可能是错误的之前,请不要继续.通过推理.

Do you see why it is dangerous? Do not proceed until you understand why this is probably wrong. Reason it through.

通常,实现一个实现两个相同"通用接口的类型是一种危险的做法,因为可能会发生非常奇怪的事情.语言和运行时并不是为了优雅地处理这种统一而设计的.例如,考虑协方差是如何工作的;如果将ark转换为IEnumerable<Animal>会发生什么?看看你能不能解决这个问题;然后尝试一下,看你是否正确.

In general, it's a dangerous practice to implement a type that implements two of "the same" generic interfaces because very weird things can happen. The language and the runtime were not designed to handle this sort of unification elegantly. Consider for example how covariance works; what happens if we cast ark to IEnumerable<Animal>? See if you can figure it out; then try it and see if you were right.

不幸的是,你的处境更糟;如果实例化GenericCollection<TCollection, TItem>以使TItemGeneralObject怎么办? 现在您已经实现了IEnumerable<GeneralObject>两次!,这确实使用户感到困惑,CLR根本不喜欢这种方式.

Unfortunately, you are in an even worse position; what if you instantiate GenericCollection<TCollection, TItem> such that TItem is GeneralObject? Now you have implemented IEnumerable<GeneralObject> twice! That's really confusing to users and the CLR does not like that at all.

更好的做法是使Ark既不实现任何接口,而公开两种方法,一种返回海龟,另一种返回长颈鹿.您应该强烈考虑在课堂上做同样的事情.更好的设计是使GenericCollection<TCollection, TItem>不实现IEnumerable<TItem>而是具有属性IEnumerable<TItem> Items { get { ... } }.

The better practice is to make Ark implement neither interface, but rather expose two methods, one which returns turtles and one which returns giraffes. You should strongly consider doing the same in your class. A better design would be to make GenericCollection<TCollection, TItem> not implement IEnumerable<TItem> but rather to have a property IEnumerable<TItem> Items { get { ... } }.

使用该设计,您可以接着执行collection.Select来获取常规对象,或者执行collection.Items.Select来获取项目,然后问题就消失了.

With that design, you can then do collection.Select to get general objects, or collection.Items.Select to get items, and the problem goes away.

这篇关于只能在IEnumerable上调用Cast和OfType的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆