Это Спарта, или нет?

Ниже приведен собеседование. Я придумал решение, но я не уверен, почему он работает.


Вопрос:

Не изменяя класс Sparta, напишите код, который делает MakeItReturnFalse return false.

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

Мое решение:

публичный класс Место   {       открытый интерфейс Sparta {}   }

Но почему Sparta в MakeItReturnFalse() относится к {namespace}.Place.Sparta вместо {namespace}.Sparta?

Ответ 1

Но почему Sparta в MakeItReturnFalse() относится к {namespace}.Place.Sparta вместо {namespace}.Sparta?

В принципе, потому что это то, что говорят правила поиска имен. В спецификации С# 5 соответствующие правила именования приведены в разделе 3.8 ( "Имена пространства имен и имена типов" ).

Первая пара пуль - усеченная и аннотированная - читайте:

  • Если имя или тип-имя имеет форму I или формы I<A1, ..., AK> [так что K = 0 в нашем случае]:
    • Если K равно нулю, а имя пространства имен или имен в объявлении универсального метода [нет, никаких общих методов]
    • В противном случае, если имя пространства имен или типа типа появляется в объявлении типа, то для каждого типа экземпляра T (§10.3.1), начиная с типа экземпляра объявления этого типа и продолжающегося с типом экземпляра каждого включение объявления класса или структуры (если есть):
      • Если K равно нулю, а объявление T включает параметр типа с именем I, то имя-пространство-тип-имя ссылается на этот параметр типа. [Неа]
      • В противном случае, если имя пространства имен или имя пространства отображается внутри тела объявления типа, а T или любой из его базовых типов содержит вложенный доступный тип с именем I и K, то имя-пространство-тип-имя ссылается на этот тип, построенный с заданными аргументами типа. [Бинго!]
  • Если предыдущие шаги были безуспешными, то для каждого пространства имен N, начиная с пространства имен, в котором происходит имя пространства имен или типа, продолжается с каждым охватывающим пространством имен (если есть) и заканчивается глобальным пространством имен, следующие шаги оцениваются до тех пор, пока объект не будет расположен:
    • Если K равно нулю, а I - это имя пространства имен в N, тогда... [Да, это будет успешным]

Таким образом, конечная маркерная точка - это то, что поднимает класс Sparta, если первая марка не находит ничего... но когда базовый класс Place определяет интерфейс Sparta, он обнаруживается до того, как мы рассмотрим класс Sparta.

Обратите внимание, что если вы создаете вложенный тип Place.Sparta класс, а не интерфейс, он все еще компилирует и возвращает false - но компилятор выдает предупреждение, поскольку он знает, что экземпляр Sparta никогда не будет экземпляр класса Place.Sparta. Аналогично, если вы поддерживаете Place.Sparta интерфейс, но создаете класс Sparta sealed, вы получите предупреждение, потому что экземпляр Sparta никогда не сможет реализовать интерфейс.

Ответ 2

При разрешении имени к его значению "сближение" определения используется для разрешения неоднозначностей. Какое бы определение не было "самым близким" - это тот, который выбран.

Интерфейс Sparta определяется в базовом классе. Класс Sparta определяется в содержащем пространстве имен. Вещи, определенные в базовом классе, "ближе", чем те, которые определены в одном и том же пространстве имен.

Ответ 3

Красивый вопрос! Я хотел бы добавить немного более длинное объяснение для тех, кто не делает С# на ежедневной основе... потому что вопрос является хорошим напоминанием о проблемах разрешения имен в целом.

Возьмите исходный код, слегка измененный следующими способами:

  • Пусть распечатывают имена типов вместо их сравнения, как в исходном выражении (т.е. 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 и вызываем три метода.

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 в суперклассе, который сначала определяется разрешением имени.

Но почему С# выбрал приоритет для интерфейсов, определенных в суперклассе в разрешении имен? @JonSkeet знает! И если вы прочтете его ответ, вы получите информацию о протоколе разрешения имен на С#.