Анонимные типы

Насколько часто у вас возникала потребность в легковесном классе, хранящем несколько взаимосвязанных значений для использования внутри определенного метода, и вы стонали от необходимости вводить целое определение типа, с приватными полями и общедоступными свойствами? Так обратитесь к анонимным типам!

С# позволяет вводить такие типы, используя неявно типизированные локальные переменные вместе с расширенным синтаксисом операции new.

Давайте посмотрим, на что это похоже:

1
2
3
4
5
6
7
8
9
10
using System;
public class EntryPoint
{
static void Main()  {
var employeelnfo = new { Name = "Joe", Id = 42 };
var customerlnfo = new { Name = " Jane", Id = "AB123" };
Console.WriteLine ( "Значение Name: {0}, значение Id: {1}", employeelnfo.Name, employeelnfo.Id );
Console.WriteLine ( "Тип employeelnfo: {0}", employeelnfo.GetType() );
Console.WriteLine ( "Тип customerlnfo: {0}", customerlnfo.GetType() );
}

Обратите внимание на интересный синтаксис в скобках после ключевого слова new внутри объявления employeelnfo. С помощью пар “имя/значение’ объявляются имена свойств внутри анонимного типа, и эти свойства инициализируются заданными значениями. В данном случае создаются два анонимных типа, каждый из которых содержит два свойства. В первом анонимном типе первое свойство имеет имя Name и относится к типу System.String, а второе — имя Id и относится к типу System.Int32.

Важно отметить, что лежащий в основе тип экземпляра созданного подобным образом является строгим типом, но его генерирует компилятор, поэтому имя данного типа неизвестно. Но как видно из следующего вывода, полученного в результате выполнения приведенного выше кода, имя этого типа можно узнать:

Значение Name: Joe, значение Id: 42
Тип employeelnfo: of_AnonymousType() N2[System.String, System.Int32]
Тип customerlnfo: of_AnonymousType() N2[System.String, System.String]

Имена сгенерированных компилятором типов специфичны для реализации, поэтому никогда не следует полагаться на них. Вдобавок, как вы заметили, они являются “недопустимыми” для компилятора; если вы попытаетесь объявить экземпляр, указав такое имя типа, то компилятор сообщит о синтаксической ошибке.

Поскольку сгенерированное компилятором имя типа неизвестно, экземпляр переменной должен быть объявлен как неявно типизированная локальная переменная с применением ключевого слова var, как это делалось в коде примера.

Также обратите внимание, что сгенерированный компилятором тип является обобщенным и принимает два параметра типа. Генерировать новый тип для каждого анонимного типа, содержащего два типа с одними и теми же именами полей, для компилятора было бы неэффективно. Приведенный выше вывод указывает на то, что действительный тип employeelnfo выглядит следующим образом:
of_AnonymousType() System.String, System.Int32>
Поскольку анонимный тип для customerlnfo содержит столько же полей с теми же именами, то сгенерированный обобщенный тип используется повторно, и тип customerlnfo будет выглядеть так:
of_AnonymousType() System.String, System.String

Если анонимный тип для customerlnfo включает поля с именами, отличными от применяемых в employeelnfo, то будет объявлен другой обобщенный анонимный тип.

Теперь, когда вы ознакомились с основами анонимных типов, рассмотрим сокращенный синтаксис для их объявления. В следующем примере обратите внимание на операторы, выделенные полужирным:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
public class ConventionalEmployeelnfо {
public ConventionalEmployeelnfо ( string Name, int Id )  {
this, name = Name; this.id = Id;
}
public string Name {
get {
return name;
}
set {
name = value;
}
public int Id {
get {
return id;
}
set {
id = value;
}
private string name;
private int id;
}
public class EntryPoint {
static void Main()  {
ConventionalEmployeelnfо oldEmployee =
new ConventionalEmployeelnfо ( "Joe", 42 );
var employeelnfo = new { oldEmployee. Name, oldEmployee. Id };
string Name = "Jane";
int Id = 1234;
var customerlnfo = new { Name, Id } ;
Console.WriteLine( "Значение Name в employeelnfo: {0}, Id: {1}", employeelnfo.Name, employeelnfo.Id );
Console.WriteLine( "Значение Name в customerlnfo: {0}, Id:  {1}", customerlnfo.Name, customerlnfo.Id );
Console.WriteLine( "Анонимным типом сейчас является: {0}", employeelnfo.GetType() );
}

В целях иллюстрации объявлен тип ConventionalEmployeelnf о, который не является анонимным. Обратите внимание, что в точке, где создается экземпляр анонимного типа для employeelnfo, не были указаны имена полей, как раньше. В данном случае компилятор использует имена свойств типа ConventionalEmployeelnf о, который является источником данных. Этот же прием работает и для локальных переменных, как видно в объявлении экземпляра customerlnfo.

В данном случае customerlnfo — анонимный тип, реализующий два свойства с именами Name и Id, доступные для чтения/записи. Объявления членов анонимных типов, использующие такой сокращенный стиль, называются инициализаторами проекции (projection initializers).

Если вы просмотрите скомпилированную сборку в ILDASM, то заметите, что сгенерированными типами для анонимных типов будут классы. Каждый такой класс помечен модификаторами private и sealed. Однако эти классы исключительно примитивны и не реализуют ничего подобного финализатору или IDisposable.

, даже будучи классами, не реализуют интерфейс IDisposable. Общее требование для типов, содержащих одноразовые (disposable) типы, заключается в том, что они тоже должны быть одноразовыми. Но поскольку анонимные типы таковыми не являются, следует избегать помещения экземпляров одноразовых типов внутрь них.

Будьте осторожны, чтобы не потерять информацию об анонимном типе. Если экземпляры анонимных типов помещены в список System. List, то как впоследствии планируется приводить их обратно к анонимному типу? Помните, что System.List хранит ссылки на System.Object. Даже несмотря на то, что анонимные типы наследуются от System.Object, каким образом привести их обратно к конкретным типам, чтобы обратиться к их свойствам?

Можно попытаться применить рефлексию, но это потребует выполнения такого объема работы, что выигрыш от использования анонимных типов будет сведен на нет.

Аналогично, если необходимо передавать экземпляры анонимных типов из функций через выходные параметры или с помощью оператора return, то их следует передавать в виде ссылок на System.Object, таким образом, избавляя переменные от полезной информации о типе. Если требуется передавать экземпляры из метода, то вместо анонимного типа следует применять явно определенный тип, такой как ConventionalEmployeelnfо.

Узнав обо всех этих ограничениях, касающихся анонимных типов, может возникнуть вопрос, а в чем вообще их польза, за исключением редких случаев внутри локального контекста? Однако выясняется, что они чрезвычайно полезны при использовании с операциями проекций в языке LINQ.

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100