Почему анонимные типы являются обобщенными?
Предположим, у вас есть анонимный тип в C#:
var x = new { A = "hello", B = 123.456 };
Вы когда-нибудь смотрели код, сгенерированный компилятором для этого фрагмента? Если вы откроете скомпилированную сборку с помощью ILDASM или с помощью любой другой утилиты, то увидите следующую мешанину в самом верху объявлений типов
.class '<>f__AnonymousType0`2'<'<A>j__TPar','<B>j__TPar'>
Какого черта? Давайте его немного упростим. Мы выбрали имена таким образом, чтобы вы никак не могли случайно использовать такие же имена непосредственно в коде C#. Заменив дурацкие имена нормальными, добавив несколько объявлений и реализаций, то этот код на языке C# будет выглядеть так:
[CompilerGenerated]
internal sealed class Anon0<TA, TB>
{
private readonly TA a;
private readonly TB b;
public TA A { get { return this.a; } }
public TB B { get { return this.b; } }
public Anon0(TA a, TB b)
{ this.a = a; this.b = b; }
// плюс реализация методов Equals, GetHashCode и ToString
}
А в месте использования мы получим следующий код:
var x = new Anon0<string, double>("hello", 123.456);
Опять, какого черта? Почему для этого кода не генерируется что-то совершенно простое, наподобие такого:
[CompilerGenerated]
internal sealed class Anon0
{
private readonly string a;
private readonly double b;
public string A { get { return this.a; } }
public double B { get { return this.b; } }
public Anon0(string a, double b)
{ this.a = a; this.b = b; }
// плюс реализация методов Equals, GetHashCode и ToString
}
Хороший вопрос. Давайте рассмотрим следующий пример.
Предположим, у вас есть библиотечная сборка, написанная не вами, со следующими типами:
public class B
{
protected class P {}
}
И где-то в вашем коде у вас есть следующее:
class D1 : B
{
void M() { var x = new { P = new B.P() }; }
}
class D2 : B
{
void M() { var x = new { P = new B.P() }; }
}
Где-то мы должны сгенерировать анонимный тип или типы. Предположим, мы решили, что два анонимных типа с одинаковыми типами и именами свойств должны относиться к одному типу. (Мы хотим, чтобы структурно идентичные анонимные типы в пределах одной сборки относились к одному и тому же типу, что позволит вывести один и тот же анонимный тип при использовании его в разных методах; мы хотим иметь возможность передавать экземпляр анонимного типа между такими методами. Возможно, в следующем году я приготовлю такой пример.)
Так где мы должны сгенерировать этот анонимный тип? Как насчет того, чтобы сделать это внутри класса D1?
class D1 : B
{
[CompilerGenerated]
??? sealed class Anon0 { public P P { get { ... } } ... }
void M() { var x = new { P = new B.P() }; }
}
Какой нам нужен модификатор доступа для типа Anon0? Это не может быть модификатор private или protected, поскольку в таком случае, этот тип нельзя будет использовать внутри класса D2. Но это не может быть public или internal, поскольку тогда мы получим public/internal тип, с публичным (public) свойством защищенного (protected) типа, что некорректно. (По той же причине это не может быть “protected и internal” или “protected или internal”). Этот класс не может иметь вообще никакого модификатора! Таким образом, анонимный тип не может быть объявлен внутри класса D1. Очевидно, по тем же причинам он не может находиться внутри D2. Он не может находиться в B, поскольку класс B находится в другой сборке. Единственным оставшимся местом является глобальное пространство имен. Однако внутренний (internal) тип самого высокого уровня не может использовать защищенный тип P. Класс P доступен только внутри наследников класса B.
Но мы можем поместить анонимный тип на верхний уровень, если он никогда не будет использовать класс P. Если мы создадим обобщенный класс Anon0<TP> и создадим его экземпляр с типом P для обобщенного параметра TP, тогда P будет использоваться только внутри D1 и D2, но при этом мы, как и хотели, будем использовать один тип.
Вместо разработки сложной эвристики для определения того, когда анонимные типы должны быть обобщенными, а в противном случае создавать их обычными типами, мы просто решили последовать общему решению. Анонимные типы всегда генерируются в виде обобщенных типов, даже когда в этом нет особой необходимости. Мы провели тщательнейшее тестирование производительности, чтобы убедиться в том, что такой подход в реальных условиях не сказывается негативно на производительности, и, как выяснилось, CLR отлично справляется с созданием обобщенных типов с большим количеством параметров типов.
И на этом, я беру тайм-аут до конца этого года. В этом году было через чур много перелетов, и я собираюсь пропустить традиционное семейное празднование Дня подарка, но думаю, мне будет очень приятно провести некоторое время в Сиэтле на этих праздниках. Я надеюсь, что у вас будут веселые и безопасные праздники в этом году, и мы снова вернемся к невероятным приключениям уже в следующем году.
Comments
Anonymous
January 15, 2011
Чет я разницы не заметил, между первым и вторым чертом.Anonymous
January 19, 2011
@Макс: Разница в параметрах класса и типах его полей. В первом случае описывается реально происходящее: код разворачивается в некий именованный обобщённый класс. Во втором случае, Эрик от лица читателя предполагает: почему бы в реальной строчке кода не разворачивать анонимный класс в обычный (не обобщённый). Чтобы потом разъяснить, почему такое решение неудачное. И всё-таки, какой же wtf приходится им городить ради такой простой вещи! Ужас ведь.