以下はインタビューの質問です。私は解決策を思いつきましたが、なぜそれが機能するのか分かりません。
質問:
Sparta
クラスを変更せずに、MakeItReturnFalse
がfalse
を返すコードを作成します。
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
を参照するのですか?
しかし、なぜ
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
がゼロで、I
がN
の名前空間の名前である場合、... [はい、そのwould成功]
つまり、最後の箇条書きはSparta
をピックアップするものですclass最初の箇条書きが何も見つからない場合...しかし、基本クラスPlace
がインターフェイスSparta
を定義するとき、それは見つけられますbeforeSparta
クラスを考慮します。
ネストされたタイプPlace.Sparta
をインターフェイスではなくクラスにすると、コンパイルされてfalse
が返されますが、コンパイラはSparta
のインスタンスがクラスPlace.Sparta
のインスタンスにならないことを知っているため、警告を発行します。同様に、Place.Sparta
をインターフェイスのままにして、Sparta
クラスをsealed
にすると、Sparta
インスタンスがインターフェイスを実装できなくなるため、警告が表示されます。
名前をその値に解決するとき、定義の「近さ」があいまいさを解決するために使用されます。どのような定義でも「最も近い」ものが選択されます。
インターフェイスSparta
は、基本クラス内で定義されます。クラスSparta
は、包含する名前空間で定義されます。基本クラス内で定義されたものは、同じ名前空間で定義されたものよりも「近い」ものです。
美しい質問です!日常的に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#の名前解決プロトコルの詳細がわかります。