Недавно меня заинтересовала структура такого древнего .Net-интерфейса как IList. Почему меня это заинтересовало — это отдельная длинная история, о которой я, наверное. расскажу в следующий раз.
Чтобы изучить этот интерфейс подробнее я сначала решил посмотреть какие же методы он декларирует. Воспользовавшись рефлексией:
… я получил следующие результаты:
Как можно легко догадаться, префиксами обозначены getter и setter свойств интерфейса, то есть у нас. помимо всех методов, есть еще три свойства: Item c возможностью записи и чтения и два reodonly свойства IsReadOnly и IsFixedSize. Кроме того, заметно непонятное дублирование названий: get_IsReadOnly//IsReadOnly, get_IsFixedSize//IsFixedSize, get_Item//set_Item//Item. Для того, чтобы разобраться с этим, пришлось несколько поменять код и кроме самих свойств класса также выдать на печать названия видов свойств. Получился следующий код:
Этот код дал мне уже более развернутое представление о том. что содержит интерфейс:
Итак, стало понятно, что get_IsReadOnly — это метод-геттер свойства IsReadOnly (которое является отдельным членом интерфейса, наравне с методом), как и get_IsFixedSize это метод вызываемый при чтении свойства IsFixedSize. Свойство Item имеет здесь и сеттер и геттер, то есть к нему можно обращаться как читая его. так и изменяя.
Поняв это я сразу задался вопросом: а что будет, если методы для свойств уже будут предопределены.
Быстро состряпав первый пример я убедился. что вот такой код не будет компилироваться:
Компилятор выдает следующую ошибку:
Более интересным оказалось второе свойство Item. Моя догадка о том, что за свойством с таким названием скрывается индексер оказалась верна и я смог состряпать второй некомпилируемый класс, в этот раз с индексером:
В этот раз описание ошибки было несколько другое, но суть проблемы была также понятна:
Однако обратные попытки «обмануть» компилятор путем явной имплементации методов, необходимых для индексера, не имели успеха. Следующие два примера классов компилировались без проблем,
Однако попытка обращения к инстанциям класса при помощи индексера приводила к ошибке компиляции:
Таким образом. комплиятор проводит проверку на наличие синтаксиса индексера во время компиляции, что не дает нам возможность воспользоваться таким хитрым способом определения собственного ииндексера.
Из проведенного небольшого расследования можно сделать следующие выводы:
Если статья понравится. то в следующей статье я расскажу чем меня заинтересовал интерфейс IList.
Чтобы изучить этот интерфейс подробнее я сначала решил посмотреть какие же методы он декларирует. Воспользовавшись рефлексией:
foreach(var tp in typeof(IList).GetMembers())
Console.WriteLine(tp.Name);
… я получил следующие результаты:
get_Item
set_Item
Add
Contains
Clear
get_IsReadOnly
get_IsFixedSize
IndexOf
Insert
Remove
RemoveAt
Item
IsReadOnly
IsFixedSize
Как можно легко догадаться, префиксами обозначены getter и setter свойств интерфейса, то есть у нас. помимо всех методов, есть еще три свойства: Item c возможностью записи и чтения и два reodonly свойства IsReadOnly и IsFixedSize. Кроме того, заметно непонятное дублирование названий: get_IsReadOnly//IsReadOnly, get_IsFixedSize//IsFixedSize, get_Item//set_Item//Item. Для того, чтобы разобраться с этим, пришлось несколько поменять код и кроме самих свойств класса также выдать на печать названия видов свойств. Получился следующий код:
foreach(var tp in typeof(IList).GetMembers())
Console.WriteLine("{0} -- {1}", tp.Name, Enum.GetName(tp.MemberType.GetType(), tp.MemberType));
Этот код дал мне уже более развернутое представление о том. что содержит интерфейс:
get_Item -- Method
set_Item -- Method
Add -- Method
Contains -- Method
Clear -- Method
get_IsReadOnly -- Method
get_IsFixedSize -- Method
IndexOf -- Method
Insert -- Method
Remove -- Method
RemoveAt -- Method
Item -- Property
IsReadOnly -- Property
IsFixedSize -- Property
Итак, стало понятно, что get_IsReadOnly — это метод-геттер свойства IsReadOnly (которое является отдельным членом интерфейса, наравне с методом), как и get_IsFixedSize это метод вызываемый при чтении свойства IsFixedSize. Свойство Item имеет здесь и сеттер и геттер, то есть к нему можно обращаться как читая его. так и изменяя.
Поняв это я сразу задался вопросом: а что будет, если методы для свойств уже будут предопределены.
Быстро состряпав первый пример я убедился. что вот такой код не будет компилироваться:
public class Test1
{
public int A { get; set; }
public int get_A()
{
return 0;
}
}
Компилятор выдает следующую ошибку:
Type 'TestArray.Test1' already reserves a member called 'get_A' with the same parameter types
Более интересным оказалось второе свойство Item. Моя догадка о том, что за свойством с таким названием скрывается индексер оказалась верна и я смог состряпать второй некомпилируемый класс, в этот раз с индексером:
public class Test2
{
public int Item { get; set; }
public int this[int num]
{
get
{
return 0;
}
}
}
В этот раз описание ошибки было несколько другое, но суть проблемы была также понятна:
The type 'TestArray.Test2' already contains a definition for 'Item'
Однако обратные попытки «обмануть» компилятор путем явной имплементации методов, необходимых для индексера, не имели успеха. Следующие два примера классов компилировались без проблем,
public class Test4
{
public int Item { get; set; }
}
public class Test3
{
public Test3 get_Item(int index)
{
return this;
}
public void set_Item(int index, Test3 value)
{
}
}
Однако попытка обращения к инстанциям класса при помощи индексера приводила к ошибке компиляции:
Cannot apply indexing with [] to an expression of type 'TestArray.Test4'
Таким образом. комплиятор проводит проверку на наличие синтаксиса индексера во время компиляции, что не дает нам возможность воспользоваться таким хитрым способом определения собственного ииндексера.
Из проведенного небольшого расследования можно сделать следующие выводы:
- С# компилятор использует довольно простой и очевидный алгоритм для генерации имен внутренних методов при использовании таких синтаксически «сахарных» конструкций как indexer и property setter/getter.
- Даже не изобретая сложных имен для своих методов можно получить проблему с коллизией имен, которую не так легко будет осознать не зная подробностей внутреннего устройства .Net.
- Свойство Item и соответствующие ему сеттеры и геттеры преобразуются компиляторов в синтаксис постфиксного оператора «квадратные скобки» (=индексер), являющегося синтаксическим сахаром для конструкции get_Item().
Если статья понравится. то в следующей статье я расскажу чем меня заинтересовал интерфейс IList.