анонимные типы
Механизм анонимных типов в С# позволяет автоматически объявить кортежный
тип при помощи простого синтаксиса.
Кортежный тип
(tuple type)
1
— это тип,
который содержит коллекцию свойств, каким-то образом связанных друг с дру-
гом. В первой строке следующего программного кода я определяю класс с двумя
свойствами (
Name
типа
String
и
Year
типа
Int32
), создаю экземпляр этого типа
и назначаю свойству
Name
значение
Jeff
, а свойству
Year
— значение 1964.
// Определение типа, создание сущности и инициализация свойств
var o1 = new { Name = "Jeff", Year = 1964 };
// Вывод свойств на консоль
Console.WriteLine("Name={0}, Year={1}", o1.Name, o1.Year); // Выводит:
// Name=Jeff, Year=1964
Здесь создается анонимный тип, потому что не был определен тип имени после
слова
new
, таким образом, компилятор автоматически создает имя типа, но не со-
1
Термин «tuple» возник как «обобщение» последовательности: single, double, triple,
quadruple, quintuple, n-tuple.
274
Глава.10 .Свойства
общает какое оно (поэтому тип и назван анонимным). Использование синтаксиса
инициализации объекта обсуждалось в предыдущем разделе. Итак, я, как разработ-
чик, не имею понятия об имени типа на этапе компиляции и не знаю, с каким типом
была объявлена переменная
o1
. Однако проблемы здесь нет — я могу использовать
механизм неявной типизации локальной переменной, о котором говорится в гла-
ве 9, чтобы компилятор определил тип по выражению в правой части оператора
присваивания (
=
).
Итак, посмотрим, что же действительно делает компилятор. Обратите внимание
на следующий код:
var o = new { property1 = expression1, ..., propertyN = expressionN };
Когда вы пишете этот код, компилятор определяет тип каждого выражения,
создает закрытые поля этих типов, для каждого типа поля создает открытые свой-
ства только для чтения и для всех этих выражений создает конструктор. Код кон-
структора инициализирует закрытые поля только для чтения путем вычисления
результирующих значений. В дополнение к этому, компилятор переопределяет
методы
Equals
,
GetHashCode
и
ToString
объекта и генерирует код внутри всех
этих методов. Класс, создаваемый компилятором, выглядит следующим образом:
[CompilerGenerated]
internal sealed class <>f__AnonymousType0<...>: Object {
private readonly t1 f1;
public t1 p1 { get { return f1; } }
...
private readonly tn fn;
public tn pn { get { return fn; } }
public <>f__AnonymousType0<...>(t1 a1, ..., tn an) {
f1 = a1; ...; fn = an; // Назначает все поля
}
public override Boolean Equals(Object value) {
// Возвращает false, если какие-либо поля не совпадают;
// иначе возвращается true
}
public override Int32 GetHashCode() {
// Возвращает хеш-код, сгенерированный из хеш-кодов полей
}
public override String ToString() {
// Возвращает пары "name = value", разделенные точками
}
}
Компилятор генерирует методы
Equals
и
GetHashCode
, чтобы экземпляры ано-
нимного типа моги размещаться в хеш-таблицах. Неизменяемые свойства, в отличие
275
Свойства.без.параметров
от свойств для чтения и записи, помогают защитить хеш-код объекта от изменений.
Изменение хеш-кода объекта, используемого в качестве ключа в хеш-таблице, мо-
жет помешать нахождению объекта. Компилятор генерирует метод
ToString
для
упрощения отладки. В отладчике Visual Studio можно навести указатель мыши на
переменную, связанную с экземпляром анонимного типа, и Visual Studio вызовет
метод
ToString
и покажет результирующую строку в окне подсказки. Кстати,
IntelliSense-окно в Visual Studio будет предлагать имена свойств в процессе на-
писания кода в редакторе — очень полезная функция.
Компилятор поддерживает два дополнительных варианта синтаксиса объявле-
ния свойства внутри анонимного типа, где на основании переменных определяются
имена и типы свойств:
String Name = "Grant";
DateTime dt = DateTime.Now;
// Анонимный тип с двумя свойствами
// 1. Строковому свойству Name назначено значение Grant
// 2. Свойству Year типа Int32 Year назначен год из dt
var o2 = new { Name, dt.Year };
В данном примере компилятор определяет, что первое свойство должно на-
зываться
Name
. Так как
Name
— это имя локальной переменной, то компилятор
устанавливает значение типа свойства аналогичного типу локальной переменной,
то есть
String
. Для второго свойства компилятор использует имя поля/свойства:
Year
.
Year
— свойство класса
DateTime
с типом
Int32
, а следовательно, свойство
Year
в анонимном типе будет относиться к типу
Int32
. Когда компилятор создает
экземпляр анонимного типа, он назначает экземпляру
Name
свойство с тем же зна-
чением, что и у локальной переменной
Name
, так что свойство
Name
будет связано
со строкой
Grant
. Компилятор назначит свойству экземпляра
Year
то же значение,
что и возвращаемое значение из
dt
свойства
Year
.
Компилятор очень разумно выясняет анонимный тип. Если компилятор видит,
что вы определили множество анонимных типов с идентичными структурами,
то он создает одно определение для анонимного типа и множество экземпляров
этого типа. Под одинаковой структурой я подразумеваю, что анонимные типы
имеют одинаковые тип и имя для каждого свойства и что эти свойства определены
в одинаковом порядке. В коде из приведенного примера тип переменной
o1
и тип
переменной
o2
одинаков, так как в двух строках кода определен анонимный тип со
свойством
Name/String
и
Year/Int32
, и
Name
стоит перед
Year
.
Раз две переменные относятся к одному типу, открывается масса полезных воз-
можностей — например, проверить, содержат ли два объекта одинаковые значения,
и присвоить ссылку на один объект переменной другого объекта:
// Совпадение типов позволяет осуществлять операции сравнения и присваивания
Console.WriteLine("Objects are equal: " + o1.Equals(o2));
o1 = o2; // Присваивание
276
Глава.10 .Свойства
Раз эти типы идентичны, то можно создать массив явных типов из анонимных
типов (о массивах см. главу 16):
// Это работает, так как все объекты имею один анонимный тип
var people = new[] {
o1, // См. ранее в этом разделе
new { Name = "Kristin", Year = 1970 },
new { Name = "Aidan", Year = 2003 },
new { Name = "Grant", Year = 2008 }
};
// Организация перебора массива анонимных типов
// (ключевое слово var обязательно).
foreach (var person in people)
Console.WriteLine("Person={0}, Year={1}", person.Name, person.Year);
Анонимные типы обычно используются с технологией языка интегрированных
запросов (Language Integrated Query, LINQ), когда в результате выполнения запро-
са создается коллекция объектов, относящихся к одному анонимному типу, после
чего производится обработка объектов в полученной коллекции. Все это делается
в одном методе. В следующем примере все файлы из папки с моими документами,
которые были изменены в последние семь дней:
String myDocuments =
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var query =
from pathname in Directory.GetFiles(myDocuments)
let LastWriteTime = File.GetLastWriteTime(pathname)
where LastWriteTime > (DateTime.Now - TimeSpan.FromDays(7))
orderby LastWriteTime
select new { Path = pathname, LastWriteTime };
foreach (var file in query)
Console.WriteLine("LastWriteTime={0}, Path={1}",
file.LastWriteTime, file.Path);
Экземпляры анонимного типа не должны выходить за пределы метода. В про-
тотипе метода не может содержаться параметр анонимного типа, так как задать
анонимный тип невозможно. По тому же принципу метод не может возвращать
ссылку на анонимный тип. Хотя экземпляр анонимного типа может интерпрети-
роваться как
Object
(все анонимные типы являются производными от
Object
),
преобразовать переменную типа
Object
обратно к анонимному типу невозможно,
потому что имя анонимного типа на этапе компиляции неизвестно. Для передачи
кортежного типа следует использовать тип
System.Tuple
, о котором речь идет
в следующем разделе.
Do'stlaringiz bilan baham: |