Это Спарта, или нет?
Ниже приведен собеседование. Я придумал решение, но я не уверен, почему он работает.
Вопрос:
Не изменяя класс 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 знает! И если вы прочтете его ответ, вы получите информацию о протоколе разрешения имен на С#.