Почему универсальные типы не могут иметь явный макет?

если кто-то пытается сделать общую структуру с [StructLayout(LayoutKind.Explicit)] атрибут, используя структуру, генерирует исключение во время выполнения:

.TypeLoadException: не удалось загрузить тип " foo "из сборки "bar", поскольку универсальные типы не могут иметь явного макета.

мне было трудно найти какие-либо доказательства того, что это ограничение даже существует. The Type.IsExplicitLayout документы явно показывают, что это разрешено и поддерживается. Кто-нибудь знает почему это запрещено? Я не могу придумать никакой причины, почему общие типы сделают его менее проверяемым. Мне кажется, что это крайний случай, который они просто не потрудились реализовать.

<!-

вот!--15-->пример почему явный общий макет был бы полезен:

public struct TaggedUnion<T1,T2>
{
    public TaggedUnion(T1 value) { _union=new _Union{Type1=value}; _id=1; }
    public TaggedUnion(T2 value) { _union=new _Union{Type2=value}; _id=2; }

    public T1 Type1 { get{ if(_id!=1)_TypeError(1); return _union.Type1; } set{ _union.Type1=value; _id=1; } }
    public T2 Type2 { get{ if(_id!=2)_TypeError(2); return _union.Type2; } set{ _union.Type2=value; _id=2; } }

    public static explicit operator T1(TaggedUnion<T1,T2> value) { return value.Type1; }
    public static explicit operator T2(TaggedUnion<T1,T2> value) { return value.Type2; }
    public static implicit operator TaggedUnion<T1,T2>(T1 value) { return new TaggedUnion<T1,T2>(value); }
    public static implicit operator TaggedUnion<T1,T2>(T2 value) { return new TaggedUnion<T1,T2>(value); }

    public byte Tag {get{ return _id; }}
    public Type GetUnionType() {switch(_id){ case 1:return typeof(T1);  case 2:return typeof(T2);  default:return typeof(void); }}

    _Union _union;
    byte _id;
    void _TypeError(byte id) { throw new InvalidCastException(/* todo */); }

    [StructLayout(LayoutKind.Explicit)]
    struct _Union
    {
        [FieldOffset(0)] public T1 Type1;
        [FieldOffset(0)] public T2 Type2;
    }
}

использование:

TaggedUnion<int, double> foo = 1;
Debug.Assert(foo.GetUnionType() == typeof(int));
foo = 1.0;
Debug.Assert(foo.GetUnionType() == typeof(double));
double bar = (double) foo;

Edit:

чтобы быть ясным, обратите внимание, что макеты не проверяются во время компиляции, даже если структура не является общей. Перекрытие ссылок и x64 различия обнаруживаются во время выполнения CLR:http://pastebin.com/4RZ6dZ3S Я спрашиваю, почему дженерики ограничены, когда проверки выполняются во время выполнения в любом случае.

3 ответов


корень проблемы-универсальность и проверяемость, а также дизайн, основанный на ограничениях типа. Правило, что мы не можем перекрывать ссылки (указатель) с типами значений, является неявным многопараметрическим ограничением. Итак, мы знаем, что CLR достаточно умен, чтобы проверить это в неродовых случаях... почему не общий? Звучит заманчиво.

правильное определение универсального типа-это тот, который поддается проверке для работы сегодня для любого типа, который существует (в пределах ограничений), и любой, который будет определено в будущем. [1] CLR через C#, Richter компилятор проверяет определение открытого универсального типа самостоятельно, учитывая любые ограничения типа, которые вы указываете, чтобы сузить возможные аргументы типа.

при отсутствии более конкретного ограничения типа, для Foo<T,U>, T и U представляют собой как объединение всех возможных типов значений и ссылок, так и interface общим для всех этих типов (база System.Object). Если мы хотим сделать T или U более конкретными, мы можем добавить ограничения первичного и вторичного типов. В последней версии C#, наиболее известные ограничения на класс или интерфейс. ограничения типа struct или primitive не поддерживаются.

мы не можем в настоящее время сказать:

  1. где только struct или value type
  2. где T, если T-герметичный тип

Ex:

public struct TaggedUnion<T1, T2>
    where T1 : SealedThing   // illegal

таким образом, у нас нет способа определить общий тип, который поддается проверке, чтобы никогда не нарушать правило перекрытия для всех типов в пределах T и U. Даже если бы мы могли ограничить struct, вы все равно можете получить struct со ссылочными полями, такими что для некоторого типа в будущем T<,> было бы неправильно.

так что мы действительно спрашиваем здесь why don't generic types allow implicit type constraints based on code within the class?; явный макет-это внутренняя деталь реализации, что накладывает ограничения на сочетания T1 и T2 являются законными. На мой взгляд, это не соответствует дизайну, который зависит от типа ограничения. Это нарушает чистый контракт системы универсального типа, как он был разработан. Итак, зачем даже проходить через проблему навязывания системы ограничений типа в дизайне в первую очередь, если мы намерены ее сломать? С таким же успехом мы могли бы выбросить его и заменить исключениями.

с текущим положением вещей:

  1. ограничения типа являются видимыми метаданными открытого универсального типа
  2. проверка универсального типа Foo<T,U> выполняется открытое определение F<,> раз. Для каждого связанного экземпляра типа Foo<t1,u1>, t1 и u1 проверяются на правильность типа в соответствии с ограничениями. Нет необходимости в почитании кода для класса и методов для Foo<t1,u1>.

все это "насколько я знаю"

нет жесткой технической причины, по которой каждый экземпляр универсального типа не может быть семантически проанализирован на правильность (C++ является доказательством этого), но, похоже, сломать дизайн в место.

TL; DR

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

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


это указано в ECMA 335 (CLI), раздел II, раздел II.10.1.2:

explicit: макет полей явно предоставляется (§II.10.7). Однако общий тип не должен иметь явной компоновки.

вы можете себе представить, как это может быть неудобно-учитывая, что размер параметра типа зависит от параметра типа, вы можете получить некоторые решительно нечетные эффекты... ссылочное поле не может перекрываться со встроенным типом значения или другим типом ссылка, например, которую было бы трудно гарантировать, как только неизвестные размеры задействованы. (Я не изучал, как это работает для 32-битных и 64-битных ссылок, у которых есть похожая, но немного другая проблема...)

Я подозреваю, что спецификация могла быть написана, чтобы сделать некоторые более подробные ограничения, но сделать ее простым общим ограничением для всех общих типов значительно проще.


дизайн .NET framework делает определенные предположения о универсальных типах, что сделает практически невозможным разрешить структуре явного макета иметь какие-либо поля, размер которых может варьироваться в зависимости от параметров универсального типа. Пожалуй, наиболее фундаментальным является следующее:--3-->

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

следовательно, для .NET было бы невозможно разрешить структуре явного макета использовать переданный универсальный тип, который не был ограничен class как тип любого поля, отличного от самого последнего. Кроме того, использование универсального типа значения, который использовал переданный универсальный тип в качестве аргумента, должно быть ограничено тем же, что и сам переданный тип.

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

С другой стороны, большинство" безопасных " случаев использования можно было бы обрабатывать лучше, имея структуру неявного макета, которая содержала общий тип, и имейте одну или несколько явно-компоновочных структур, расположенных внутри него. Такой подход мог бы семантически сделать все, что можно было бы сделать с явными структурами компоновки. Единственным неудобством было бы добавить дополнительный уровень косвенности в исходный код при доступе к вложенным членам, и средством для этого было бы не разрешить общие структуры явного макета, а скорее предоставить средство, с помощью которого структура, содержащая другую структуру, может создавать псевдонимы для внутренний-структура членов.

пример:

[StructLayout(LayoutKind.Explicit)]
public struct _UnionLongAnd2Ints
{
    [FieldOffset(0)] public int LowerWord;
    [FieldOffset(4)] public int UpperWord;
    [FieldOffset(0)] public long Value;
}
public struct LongTwoIntsUnionAndSomethingElse<T>
{
  UnionLongAnd2Ints UnionPart;
  T OtherPart;
}

здесь общая структура содержит 64-разрядное значение, которое накладывается на два 32-разрядных значения, но оно должно быть проверяемым, потому что явно выложенная часть не имеет ничего общего.