web-dev-qa-db-ja.com

これはスパルタですか?

以下はインタビューの質問です。私は解決策を思いつきましたが、なぜそれが機能するのか分かりません。


質問:

Spartaクラスを変更せずに、MakeItReturnFalsefalseを返すコードを作成します。

public class Sparta : Place
{
    public bool MakeItReturnFalse()
    {
        return this is Sparta;
    }
}

私の解決策:(SPOILER)

public class Place
{
public interface Sparta { }
}

しかし、なぜMakeItReturnFalse()Sparta{namespace}.Place.Spartaではなく{namespace}.Spartaを参照するのですか?

121
budi

しかし、なぜMakeItReturnFalse()Sparta{namespace}.Place.Spartaではなく{namespace}.Spartaを参照するのですか?

基本的に、それが名前検索規則の言うことだからです。 C#5仕様では、関連する命名規則はセクション3.8(「名前空間と型名」)にあります。

最初のいくつかの箇条書き-切り捨てと注釈付き-を読む:

  • Namespace-or-type-nameの形式がIまたはI<A1, ..., AK>の形式の場合[この場合K = 0]
    • Kがゼロで、namespace-or-type-nameがジェネリックメソッド宣言内にある場合[nope、no generic methods]
    • それ以外の場合、namespace-or-type-nameが型宣言内に現れる場合、各インスタンス型T(§10.3.1)について、その型宣言のインスタンス型から始まり、各囲みクラスのインスタンス型または構造体宣言(ある場合):
      • Kがゼロで、Tの宣言にIという名前の型パラメーターが含まれている場合、namespace-or-type-nameはその型パラメーターを参照します。 [Nope]
      • それ以外の場合、namespace-or-type-nameが型宣言の本体内にあり、Tまたはそのベース型のいずれかに、IおよびK型パラメーターを持つネストされたアクセス可能な型が含まれている場合、 namespace-or-type-nameは、指定された型引数で構築された型を参照します。 [Bingo!]
  • 前の手順が失敗した場合、各名前空間Nについて、namespace-or-type-nameが発生する名前空間から開始し、囲んでいる各名前空間(存在する場合)を継続し、グローバル名前空間で終了し、次の手順を実行しますエンティティが見つかるまで評価されます:
    • Kがゼロで、INの名前空間の名前である場合、... [はい、そのwould成功]

つまり、最後の箇条書きはSpartaをピックアップするものですclass最初の箇条書きが何も見つからない場合...しかし、基本クラスPlaceがインターフェイスSpartaを定義するとき、それは見つけられますbeforeSpartaクラスを考慮します。

ネストされたタイプPlace.Spartaをインターフェイスではなくクラスにすると、コンパイルされてfalseが返されますが、コンパイラはSpartaのインスタンスがクラスPlace.Spartaのインスタンスにならないことを知っているため、警告を発行します。同様に、Place.Spartaをインターフェイスのままにして、Spartaクラスをsealedにすると、Spartaインスタンスがインターフェイスを実装できなくなるため、警告が表示されます。

118
Jon Skeet

名前をその値に解決するとき、定義の「近さ」があいまいさを解決するために使用されます。どのような定義でも「最も近い」ものが選択されます。

インターフェイスSpartaは、基本クラス内で定義されます。クラスSpartaは、包含する名前空間で定義されます。基本クラス内で定義されたものは、同じ名前空間で定義されたものよりも「近い」ものです。

22
Servy

美しい質問です!日常的にC#を実行していない人のために、少し長い説明を追加したいと思います。この質問は、一般的な名前解決の問題を思い出させるものだからです。

次の方法でわずかに変更された元のコードを使用します。

  • 元の式(つまりreturn this is Sparta)のように型名を比較する代わりに、型名を出力してみましょう。
  • AthenaスーパークラスでインターフェイスPlaceを定義して、インターフェイスの名前解決を説明します。
  • また、すべてを非常に明確にするために、thisクラスにバインドされているSpartaの型名も出力します。

コードは次のようになります。

public class Place {
    public interface Athena { }
}

public class Sparta : Place
{
    public void printTypeOfThis()
    {
        Console.WriteLine (this.GetType().Name);
    }

    public void printTypeOfSparta()
    {
        Console.WriteLine (typeof(Sparta));
    }

    public void printTypeOfAthena()
    {
        Console.WriteLine (typeof(Athena));
    }
}

Spartaオブジェクトを作成し、3つのメソッドを呼び出します。

public static void Main(string[] args)
    {
        Sparta s = new Sparta();
        s.printTypeOfThis();
        s.printTypeOfSparta();
        s.printTypeOfAthena();
    }
}

得られる出力は次のとおりです。

Sparta
Athena
Place+Athena

ただし、Placeクラスを変更し、インターフェイスSpartaを定義すると:

   public class Place {
        public interface Athena { }
        public interface Sparta { } 
    }

次に、このSparta-インターフェイス-が最初に名前検索メカニズムで利用可能になり、コードの出力が次のように変更されます。

Sparta
Place+Sparta
Place+Athena

そのため、名前解決によって最初に見つかるスーパークラスでSpartaインターフェイスを定義するだけで、MakeItReturnFalse関数定義の型比較を効果的に台無しにしてしまいました。

しかし、C#が名前解決でスーパークラスで定義されたインターフェイスを優先することを選択したのはなぜですか? @JonSkeetは知っています!また、彼の答えを読むと、C#の名前解決プロトコルの詳細がわかります。

1
mircealungu