tbo*_*lon 5 c# enums overloading indexer
考虑以下代码:
namespace MyApp
{
using System;
using System.Collections.ObjectModel;
class Program
{
static void Main(string[] args)
{
var col = new MyCollection();
col.Add(new MyItem { Enum = MyEnum.Second });
col.Add(new MyItem { Enum = MyEnum.First });
var item = col[0];
Console.WriteLine("1) Null ? {0}", item == null);
item = col[MyEnum.Second];
Console.WriteLine("2) Null ? {0}", item == null);
Console.ReadKey();
}
}
class MyItem { public MyEnum Enum { get; set; } }
class MyCollection : Collection<MyItem>
{
public MyItem this[MyEnum val]
{
get
{
foreach (var item in this) { if (item.Enum == val) return item; }
return null;
}
}
}
enum MyEnum
{
Default = 0,
First,
Second
}
}
Run Code Online (Sandbox Code Playgroud)
我很惊讶地看到以下结果:
1) Null ? True
2) Null ? False
Run Code Online (Sandbox Code Playgroud)
我的第一个期望是因为我传递的是int,应该使用默认索引器,并且第一次调用应该成功.
相反,似乎enum总是调用期望a的重载(即使将0转换为int),并且测试失败.
编辑:解决方法似乎是将集合转换为集合,请参阅此答案.
所以:
说明
使用此代码,我们面临两个问题:
有关更精确(和更好的表达)的答案,请参阅以下链接:
Eri*_*ert 10
这里的各种答案已经解决了.总结并提供解释性材料的一些链接:
首先,字面零可以转换为任何枚举类型.这是因为我们希望您能够将任何"flags"枚举初始化为零值,即使没有可用的枚举值也是如此.(如果我们不得不重新做一遍,我们可能不会实现这个功能;相反,default(MyEnum)如果你想这样做,我们会说只使用表达式.)
事实上,常量,而不仅仅是字面常量零,可以转换为任何枚举类型.这是为了向后兼容一个历史悠久的编译器错误,这个错误比修复更加昂贵.
有关详细信息,请参阅
http://blogs.msdn.com/b/ericlippert/archive/2006/03/28/the-root-of-all-evil-part-one.aspx
http://blogs.msdn.com/b/ericlippert/archive/2006/03/29/the-root-of-all-evil-part-two.aspx
然后确定你的两个索引器 - 一个采用int和一个采用枚举 - 在传递文字零时都是适用的候选者.那么问题是哪个是更好的候选人.这里的规则很简单:如果任何候选者适用于派生类,那么它自动优于基类中的任何候选者.因此你的枚举索引器获胜.
这种有点违反直觉的规则的原因是双重的.首先,似乎有意义的是,编写派生类的人比编写基类的人拥有更多的信息.毕竟,他们专门研究了基类,所以在给出选择时你想要调用最专业的实现似乎是合理的,即使它不是完全匹配的.
第二个原因是这种选择减轻了脆弱的基类问题.如果向基类添加了一个索引器,该索引器碰巧比派生类上的索引更好,那么对于派生类的用户来说,用于选择派生类的代码突然开始选择基类将是意外的.
看到
http://blogs.msdn.com/b/ericlippert/archive/2007/09/04/future-breaking-changes-part-three.aspx
有关此问题的更多讨论.
正如James正确指出的那样,如果你在类上创建一个带有int的新索引器,那么重载决策问题会变得更好:从零转换为枚举,或从零转换为int.由于两个索引器属于同一类型而后者是精确的,因此它获胜.
似乎因为它enum是int兼容的,它更喜欢使用隐式转换enum,int并选择采用类中定义的枚举的索引器.
(UPDATE:真正的原因被证明是,它是从更喜欢隐式转换的const int的0向enum在超类int索引,因为两者的转化是相等的,所以选择了前者的转换,因为它是更派生类型的内部:MyCollection.)
我不确定为什么会这样做,当显然有一个公共索引器有一个int参数Collection<T>- 对于Eric Lippert一个很好的问题,如果他正在观察这个,因为他有一个非常确定的答案.
不过,我确实验证过,如果你在新类中重新定义int索引器,如下所示,它将起作用:
public class MyCollection : Collection<MyItem>
{
public new MyItem this[int index]
{
// make sure we get Collection<T>'s indexer instead.
get { return base[index]; }
}
}
Run Code Online (Sandbox Code Playgroud)
从规范看起来,文字0总是可以隐式转换为enum:
13.1.3隐式枚举转换隐式枚举转换允许将decimal-integer-literal 0转换为任何枚举类型.
因此,如果你把它称为
int index = 0;
var item = col[index];
Run Code Online (Sandbox Code Playgroud)
它会工作,因为你强迫它选择int索引器,或者如果你使用非零文字:
var item = col[1];
Console.WriteLine("1) Null ? {0}", item == null);
Run Code Online (Sandbox Code Playgroud)
会工作,因为1无法隐式转换为enum
它仍然很奇怪,我允许你考虑索引器Collection<T>应该同样可见.但我会说它看起来像enum你的子类中的索引器,并且知道0可以隐式地转换为int并满足它并且不会上升到类层次结构链.
这似乎得到7.4.2 Overload Resolution了规范中的部分的支持,其中部分说明:
如果派生类中的任何方法适用,则基类中的方法不是候选方法
这让我相信,既然子类索引器工作,它甚至不检查基类.