使用枚举重载索引器:无法使用默认索引器

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),并且测试失败.

  1. 有人可以向我解释这种行为吗?
  2. 并提供一个解决方法来维护两个索引器:一个是索引,一个是枚举?

编辑:解决方法似乎是将集合转换为集合,请参阅此答案.

所以:

  1. 为什么编译器选择最"复杂"的重载而不是最明显的重载(尽管它是一个继承的重载)?索引器是否被视为native int方法?(但是没有警告您隐藏父索引器的事实)

说明

使用此代码,我们面临两个问题:

  1. 0值始终可以转换为任何枚举.
  2. 运行时始终通过在继承挖掘之前检查底层类来开始,因此选择了枚举索引器.

有关更精确(和更好的表达)的答案,请参阅以下链接:

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.由于两个索引器属于同一类型而后者是精确的,因此它获胜.


Jam*_*are 5

似乎因为它enumint兼容的,它更喜欢使用隐式转换enum,int并选择采用类中定义的枚举的索引器.

(UPDATE:真正的原因被证明是,它是从更喜欢隐式转换的const int0enum在超类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了规范中的部分的支持,其中部分说明:

如果派生类中的任何方法适用,则基类中的方法不是候选方法

这让我相信,既然子类索引器工作,它甚至不检查基类.