考虑以下代码:
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
}
}
我很惊讶地看到以下结果:
1) Null ? True
2) Null ? False
我的第一个期望是因为我传递的是int
,所以应该使用默认索引器,并且第一次调用应该已成功。
相反,似乎总是调用期望enum
的重载(即使将0转换为int),并且测试失败。
编辑:解决方法似乎是将集合转换为集合,请参阅this answer。
所以:
解释
使用此代码,我们面临两个问题:
有关更精确(和更好的表达)的答案,请参阅以下链接:
答案 0 :(得分: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。由于两个索引器属于同一类型而后者是 exact ,因此它将获胜。
答案 1 :(得分:5)
似乎因为enum
是int
- 兼容,所以它更喜欢使用从enum
到int
的隐式转换,并选择采用枚举定义的索引器在你的班上
(更新:真正的原因是它更喜欢从const int
0
到enum
的隐式转换超过超级-class int
索引器,因为两个转换都是相同的,所以选择前一个转换,因为它在更多派生类型中:MyCollection
。)
我不确定为什么会这样做,当int
显然有一个Collection<T>
参数的公共索引器时 - 对于Eric Lippert来说这是一个很好的问题d有一个非常确定的答案。
但我确实验证了如果你在新类中重新定义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]; }
}
}
从规范看,文字0
似乎总是隐式转换为enum
:
13.1.3隐式枚举转换隐式枚举 转换允许将decimal-integer-literal 0转换为 任何枚举类型。
因此,如果您将其称为
int index = 0;
var item = col[index];
它会起作用,因为你强迫它选择int索引器,或者你使用的是非零文字:
var item = col[1];
Console.WriteLine("1) Null ? {0}", item == null);
自1
无法隐式转换为enum
它仍然很奇怪,我允许你考虑来自Collection<T>
的索引器应该同样可见。但我会说它看起来像你的子类中的enum
索引器,并且知道0
可以隐式地转换为int
并且满足它并且不会上升到类 - 等级链。
这似乎得到规范中7.4.2 Overload Resolution
部分的支持,其中部分说明:
如果a中的任何方法,和基类中的方法不是候选者 派生类适用
这使我相信,由于子类索引器起作用,它甚至不检查基类。
答案 2 :(得分:2)
在C#中,内容0
始终可以隐式转换为任何枚举类型。您已经重载了索引器,因此编译器会选择最具体的重载。请注意,这在编译期间发生。所以,如果你愿意写:
int x = 0;
var item = col[x];
现在编译器没有推断第二行x
总是等于0
,所以它会选择原始的this[int value]
重载。 (编译器不是很聪明: - ))
在C#的早期版本中,只有文字0
会隐式转换为枚举类型。 Since version 3.0,所有计算为0
的常量表达式都可以隐式地转换为枚举类型。这就是为什么偶然(int)0
被归为枚举。
更新:有关重载决策的额外信息
我一直认为重载解析只是看了方法签名,但它似乎也更喜欢派生类中的方法。例如,考虑以下代码:
public class Test
{
public void Print(int number)
{
Console.WriteLine("Number: " + number);
}
public void Print(Options option)
{
Console.WriteLine("Option: " + option);
}
}
public enum Options
{
A = 0,
B = 1
}
这将导致以下行为:
t.Print(0); // "0"
t.Print(1); // "1"
t.Print(Options.A); // "A"
t.Print(Options.B); // "B"
但是,如果您创建基类并将Print(int)
重载移动到基类,则Print(Options)
重载将具有更高的首选项:
public class TestBase
{
public void Print(int number)
{
Console.WriteLine("Number: " + number);
}
}
public class Test : TestBase
{
public void Print(Options option)
{
Console.WriteLine("Option: " + option);
}
}
现在行为发生了变化:
t.Print(0); // "A"
t.Print(1); // "1"
t.Print(Options.A); // "A"
t.Print(Options.B); // "B"