これはスパルタですか、それともそうですか?


121

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


質問:

Spartaクラスを変更せずに、MakeItReturnFalsereturn を実行するコードを記述しますfalse

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

私の解決策:(SPOILER)

public class Place
{
public interface Sparta { }
}

しかし、なぜの代わりにSpartaMakeItReturnFalse()言及する{namespace}.Place.Sparta{namespace}.Spartaですか?


5
ソリューションにスポイラーアラートなどを追加していただけませんか。私はがっかりしているので、自分で解決する機会がありません。質問は本当に素晴らしいです。
Karolis Kajenas

1
私は最初にスポイラータグを含めましたが、この投稿の全期間を通じてコミュニティによって何度も編集されました。申し訳ありません。
ブディ

1
この投稿のタイトルが嫌いです。codegolf.SEにより適しています。問題を実際に説明するものに変更できますか?
Supuhstar

3
かわいいパズル。ひどいインタビューの質問ですが、かわいいパズルです。今、あなたはどのように、なぜこの作品を知っていることを、あなたはこの困難バージョンでショットを取る必要があります。stackoverflow.com/q/41319962/88656を
エリックリペットに

回答:


117

しかし、なぜの代わりにSpartaMakeItReturnFalse()言及する{namespace}.Place.Sparta{namespace}.Spartaですか?

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

最初のいくつかの箇条書き-切り捨てられ、注釈が付けられています-読む:

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

だから、最終的な箇条書きを拾っ何であるSparta クラスを最初の弾丸は何も見つからない場合は...しかし、基底クラスが時にPlaceインターフェイスを定義しSparta、それを見つけます前に、私たちが考えるSpartaクラスを。

入れ子になった型をPlace.Spartaインターフェイスではなくクラスにしても、コンパイルして返されますが、のインスタンスはクラスのfalseインスタンスにSpartaはならないことがわかっているため、コンパイラは警告を発行しますPlace.Sparta。同様にPlace.Sparta、インターフェイスを保持しながらSpartaクラスを作成すると、インスタンスがインターフェイスを実装できsealedないため、警告が表示Spartaされます。


2
別のランダムな観察:元のSpartaクラスを使用して、をthis is Place返しますtrue。ただし、クラスに追加public interface Place { }すると、が返されます。頭を回転させます。Spartathis is Placefalse
ブディ

@budi:そうです、やはり、以前の箇条書きがPlaceインターフェースとして見つかります。
ジョンスキート

22

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

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


1
名前の検索がこのように機能しなかった場合を想像してみてください。次に、誰かが偶然同じ名前のトップレベルのクラスを追加した場合、内部クラスを含む作業コードが壊れます。
dan04

1
@ dan04:しかし、代わりに、ネストされたクラスを含まない作業コードは、誰かがトップレベルのクラスと同じ名前のネストされたクラスを追加した場合、壊れます。したがって、これは完全に「成功する」シナリオではありません。
ジョンスキート

1
@JonSkeetそのようなネストされたクラスの追加は、作業コードが影響を受ける合理的な理由がある領域の変更であり、変更を監視すると思います。まったく関係のないトップレベルのクラスを追加することははるかに削除されます。
Angewはもはや誇りSOのではありません

2
@JonSkeetしかし、それは少し異なる形での脆性基本クラスの問題だけではありませんか?
ジョアン・メンデス

1
@JoãoMendes:はい、ほとんど。
ジョンスキート2017年

1

美しい質問!日常的に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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.