5

次のコードを検討します。

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ので、デフォルトのインデクサーを使用する必要があり、最初の呼び出しが成功するはずだったということでした。

代わりに、(0をintとしてキャストする場合でも)anを期待するオーバーロードenumが常に呼び出され、テストが失敗するようです。

  1. 誰かが私にこの振る舞いを説明できますか?
  2. そして、2つのインデクサーを維持するための回避策を提供します。1つはインデックスごと、もう1つは列挙型用ですか。

編集:回避策はコレクションをコレクションとしてキャストしているようです。この回答を参照してください。

それで:

  1. コンパイラが最も明白なものではなく、最も「複雑な」オーバーロードを選択するのはなぜですか(継承されたものであるにもかかわらず)?インデクサーはネイティブintメソッドと見なされますか?(ただし、親インデクサーを非表示にするという事実についての警告はありません)

説明

このコードでは、2つの問題に直面しています。

  1. 0の値は、常に任意の列挙型に変換できます。
  2. ランタイムは、継承を掘り下げる前に常に最下位クラスをチェックすることから開始するため、列挙型インデクサーが選択されます。

より正確な(そしてより適切に定式化された)回答については、次のリンクを参照してください。

4

3 に答える 3

11

ここでのさまざまな答えがそれを示唆しています。要約して、説明資料へのリンクをいくつか提供するには:

まず、リテラルのゼロは任意の列挙型に変換できます。これは、使用可能なゼロ列挙値がない場合でも、「フラグ」列挙をゼロ値に初期化できるようにするためです。(もう一度やり直す必要がある場合は、おそらくこの機能を実装しません。むしろ、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

これにより、2つのインデクサー(1つはintを受け取り、もう1つはenumを受け取る)が、リテラルのゼロを渡されたときに両方とも適用可能な候補であることが確立されます。問題は、どちらがより良い候補であるかということです。ここでのルールは単純です。派生クラスに適用可能な候補がある場合、基本クラスのどの候補よりも自動的に優れています。したがって、列挙型インデクサーが優先されます。

このやや直感に反するルールの理由は2つあります。まず、派生クラスを作成した人は、基本クラスを作成した人よりも多くの情報を持っていることは理にかなっているようです。結局のところ、彼らは基本クラスを専門化しているので、完全に一致していなくても、選択肢が与えられたときに可能な限り最も専門的な実装を呼び出したいと思うのは当然のようです。

2番目の理由は、この選択によって脆弱な基本クラスの問題が軽減されることです。派生クラスのインデクサーよりも一致するインデクサーを基本クラスに追加した場合、派生クラスのユーザーは、派生クラスを選択していたコードが突然基本クラスの選択を開始することは予期していません。

見る

http://blogs.msdn.com/b/ericlippert/archive/2007/09/04/future-breaking-changes-part-three.aspx

この問題の詳細については。

Jamesが正しく指摘しているように、クラスでintを取る新しいインデクサーを作成すると、オーバーロード解決の問題は、ゼロから列挙型への変換、またはゼロからintへの変換のどちらが良いかということになります。両方のインデクサーは同じタイプであり、後者は正確であるため、勝ちます。

于 2011-09-19T21:23:03.360 に答える
5

enumは互換性があるため、からへintの暗黙的な変換を使用することを好み、クラスで定義された列挙型を取得するインデクサーを選択するようです。 enumint

更新:本当の原因は、両方の変換が等しいため、スーパークラスインデクサーよりもconst intof0からoverへの暗黙の変換を優先していることが判明しました。したがって、より派生した型の内部にあるため、前者の変換が選択されます。。)enumintMyCollection

なぜそれがこれを行うのかはわかりませんが、intそこから議論のある公開インデクサーが明らかに存在Collection<T>する場合、エリック・リッパートがこれを見ているのであれば、彼は非常に決定的な答えを持っているので、良い質問です。

ただし、新しいクラスで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-literal0を任意の列挙型に変換できます。

したがって、あなたがそれを次のように呼んでいた場合

        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これは、仕様のセクションでサポートされているようです。

派生クラスのメソッドが適用可能な場合、基本クラスのメソッドは候補になりません。

これは、サブクラスインデクサーが機能するため、基本クラスもチェックしないと私に信じさせます。

于 2011-09-19T17:48:06.823 に答える
2

C#では、定数0は常に暗黙的に任意の列挙型に変換できます。インデクサーをオーバーロードしたため、コンパイラーは最も具体的なオーバーロードを選択します。これはコンパイル中に発生することに注意してください。したがって、次のように記述します。

int x = 0;
var item = col[x];

xこれで、コンパイラは2行目で常に等しいとは推測しない0ため、元のthis[int value]オーバーロードを選択します。(コンパイラはあまり賢くありません:-))

C#の初期のバージョンでは、リテラルのみ0が暗黙的に列挙型にキャストされていました。バージョン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"
于 2011-09-19T18:25:33.713 に答える