Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
Определение анонимного типа
Анонимный тип определяется с использованием ключевого слова var (см. главу 3) в сочетании с синтаксисом инициализации объектов (см. главу 5). Ключевое слово var должно применяться из-за того, что компилятор будет автоматически генерировать новое определение класса на этапе компиляции (причем имя этого класса никогда не встретится в коде С#). Синтаксис инициализации применяется для сообщения компилятору о необходимости создания в новом типе закрытых поддерживающих полей и (допускающих только чтение) свойств.
В целях иллюстрации создайте новый проект консольного приложения по имени AnonymousTypes. Затем добавьте в класс Program показанный ниже метод, который формирует новый тип на лету, используя данные входных параметров:
static void BuildAnonymousType( string make, string color, int currSp )
{
// Построить анонимный тип с применением входных аргументов.
var car = new { Make = make, Color = color, Speed = currSp };
// Обратите внимание, что теперь этот тип можно
// использовать для получения данных свойств!
Console.WriteLine("You have a {0} {1} going {2} MPH",
car.Color, car.Make, car.Speed);
// Анонимные типы имеют специальные реализации каждого
// виртуального метода System.Object. Например:
Console.WriteLine("ToString() == {0}", car.ToString());
}
Обратите внимание, что помимо помещения кода внутрь функции анонимный тип можно также создавать непосредственно в строке:
Console.WriteLine("***** Fun with Anonymous Types *****n");
// Создать анонимный тип, представляющий автомобиль.
var myCar = new { Color = "Bright Pink", Make = "Saab",
CurrentSpeed = 55 };
// Вывести на консоль цвет и производителя.
Console.WriteLine("My car is a {0} {1}.", myCar.Color, myCar.Make);
// Вызвать вспомогательный метод для построения
// анонимного типа с указанием аргументов.
BuildAnonymousType("BMW", "Black", 90);
Console.ReadLine();
В настоящий момент достаточно понимать, что анонимные типы позволяют быстро моделировать "форму" данных с небольшими накладными расходами. Они являются лишь способом построения на лету нового типа данных, который поддерживает базовую инкапсуляцию через свойства и действует в соответствии с семантикой на основе значений. Чтобы уловить суть последнего утверждения, давайте посмотрим, каким образом компилятор C# строит анонимные типы на этапе компиляции, и в особенности — как он переопределяет члены System.Object.
Внутреннее представление анонимных типов
Все анонимные типы автоматически наследуются от System.Object и потому поддерживают все члены, предоставленные этим базовым классом. В результате можно вызывать метод ToString(), GetHashCode(), Equals() или GetType() на неявно типизированном объекте myCar. Предположим, что в классе Program определен следующий статический вспомогательный метод:
static void ReflectOverAnonymousType(object obj)
{
Console.WriteLine("obj is an instance of: {0}",
obj.GetType().Name);
Console.WriteLine("Base class of {0} is {1}",
obj.GetType().Name, obj.GetType().BaseType);
Console.WriteLine("obj.ToString() == {0}", obj.ToString());
Console.WriteLine("obj.GetHashCode() == {0}",
obj.GetHashCode());
Console.WriteLine();
}
Пусть вы вызвали метод ReflectOverAnonymousType(), передав ему объект myCar в качестве параметра:
Console.WriteLine("***** Fun with Anonymous Types *****n");
// Создать анонимный тип, представляющий автомобиль.
var myCar = new {Color = "Bright Pink", Make = "Saab",
CurrentSpeed = 55};
// Выполнить рефлексию того, что сгенерировал компилятор.
ReflectOverAnonymousType(myCar);
...
Console.ReadLine();
Вывод будет выглядеть примерно так:
***** Fun with Anonymous Types *****
obj is an instance of: <>f__AnonymousType0`3
Base class of <>f__AnonymousType0`3 is System.Object
obj.ToString() = { Color = Bright Pink, Make = Saab, CurrentSpeed = 55 }
obj.GetHashCode() = -564053045
Первым делом обратите внимание в примере на то, что объект myCar имеет тип <>f__AnonymousType0`3 (в вашем выводе имя типа может быть другим). Помните, что имя, назначаемое типу, полностью определяется компилятором и не доступно в коде C# напрямую.
Пожалуй, наиболее важно здесь то, что каждая пара "имя-значение", определенная с использованием синтаксиса инициализации объектов, отображается на идентично именованное свойство, доступное только для чтения, и соответствующее закрытое поддерживающее поле, которое допускает только инициализацию. Приведенный ниже код C# приблизительно отражает сгенерированный компилятором класс, применяемый для представления объекта myCar (его можно просмотреть посредством утилиты ildasm.exe):
private sealed class <>f__AnonymousType0'3'<'<Color>j__TPar',
'<Make>j__TPar', <CurrentSpeed>j__TPar>'
extends [System.Runtime][System.Object]
{
// Поля только для инициализации.
private initonly <Color>j__TPar <Color>i__Field;
private initonly <CurrentSpeed>j__TPar <CurrentSpeed>i__Field;
private initonly <Make>j__TPar <Make>i__Field;
// Стандартный конструктор.
public <>f__AnonymousType0(<Color>j__TPar Color,
<Make>j__TPar Make, <CurrentSpeed>j__TPar CurrentSpeed);
// Переопределенные методы.
public override bool Equals(object value);
public override int GetHashCode();
public override string ToString();
// Свойства только для чтения.
<Color>j__TPar Color { get; }
<CurrentSpeed>j__TPar CurrentSpeed { get; }
<Make>j__TPar Make { get; }
}
Реализация методов ToString() и GetHashCode()
Все анонимные типы автоматически являются производными от System.Object и предоставляют переопределенные версии методов Equals(), GetHashCode() и ToString(). Реализация ToString() просто строит строку из пар "имя-значение". Вот пример:
public override string ToString()
{
StringBuilder builder = new StringBuilder();
builder.Append("{ Color = ");
builder.Append(this.<Color>i__Field);
builder.Append(", Make = ");
builder.Append(this.<Make>i__Field);
builder.Append(", CurrentSpeed = ");
builder.Append(this.<CurrentSpeed>i__Field);
builder.Append(" }");
return builder.ToString();
}
Реализация GetHashCode() вычисляет хеш-значение, используя каждую переменную-член анонимного типа в качестве входных данных для типа System.Collections.Generic.EqualityComparer<T>. С такой реализацией GetHashCode() два анонимных типа будут выдавать одинаковые хеш-значения тогда (и