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


121

Следующее - вопрос интервью. Я нашел решение, но не знаю, почему оно работает.


Вопрос:

Не изменяя Spartaкласс, напишите код, MakeItReturnFalseвозвращающий результат false.

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

Мое решение: (СПОЙЛЕР)

public class Place
{
public interface Sparta { }
}

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


5
Не могли бы вы добавить Spoiler Alert или что-то еще в решение? Я так разочарован, что не могу решить эту проблему самостоятельно. Вопрос действительно потрясающий.
Каролис Кайенас

1
Изначально я включил теги спойлеров, однако сообщество несколько раз редактировало их за время существования этого поста. Извини за это.
budi

1
Мне не нравится название этого поста; это больше подходит для codegolf.SE. Можем ли мы изменить его на что-то, что действительно описывает вопрос?
Supuhstar

3
Симпатичная головоломка. Ужасный вопрос для интервью, но милая головоломка. Теперь, когда вы знаете, как и почему это работает, вам стоит попробовать эту более сложную версию: stackoverflow.com/q/41319962/88656
Эрик Липперт,

Ответы:


117

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

В основном потому, что так говорят правила поиска имени. В спецификации C # 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класс . Однако добавление к классу приводит к возврату . Голова кружится. this is Placetruepublic interface Place { }Spartathis is Placefalse
budi

@budi: Верно, потому что, опять же, в Placeкачестве интерфейса используется предыдущий пункт .
Джон Скит

22

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

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


1
И представьте, если бы поиск имени не работал таким образом. Тогда рабочий код, содержащий внутренний класс, будет поврежден, если кто-нибудь добавит класс верхнего уровня с тем же именем.
dan04

1
@ dan04: Но вместо этого рабочий код, который не содержит вложенного класса, будет сломан, если кто-нибудь добавит вложенный класс с тем же именем, что и класс верхнего уровня. Так что это не совсем сценарий «полной победы».
Джон Скит

1
@JonSkeet Я бы сказал, что добавление такого вложенного класса - это изменение в области, на которую рабочий код имеет разумные причины, и следите за изменениями. Добавление совершенно несвязанного класса верхнего уровня гораздо дальше удалено.
Энгью больше не гордится SO

2
@JonSkeet Разве это не просто проблема хрупкого базового класса в немного другом обличье?
Жуан Мендес

1
@ JoãoMendes: Да, в значительной степени.
Джон Скит

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объект и вызываем три метода.

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 #.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.