Why don't generic types have an explicit layout?

If you try to create a general structure with the [ StructLayout ( LayoutKind .Explicit)] attribute, using struct throws an exception at runtime:

System.TypeLoadException: Unable to load type 'foo' from assembly 'bar' because generic types cannot have an explicit layout.

It was difficult for me to find evidence that this limitation exists. Type.IsExplicitLayout Documents strongly imply that they are allowed and supported. Does anyone know why this is not allowed? I can't think of any reason why generic types make it less verifiable. This strikes me as a regional case, which they simply did not bother to implement.

Here is an example of why an explicit generic layout is useful:

 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; } } 

using:

 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:

To be clear, note that layouts are not checked at compile time, even if the structure is not shared. The second link match and x64 differences are found in the CLR runtime: http://pastebin.com/4RZ6dZ3S I ask why generics are limited when checks are performed at runtime anyway.

+7
generics c # clr unsafe
source share
3 answers

The root of the problem is versatility and verifiability, as well as design based on type constraints. The rule that we cannot overlap references (pointer) with type values ​​is an implicit, multi-parameter constraint. So, we know that the CLR is smart enough to test this in non-general cases ... why not a generic one? That sounds attractive.

A correct definition of a general type is one that is subject to verification today for any type that exists (within the limits) and any that will be defined in the future. [1] CLR via C #, Richter. The compiler itself checks the definition of an open generic type, taking into account any type constraints that you specify in order to narrow down the possible type arguments.

In the absence of a more specific type restriction for Foo<T,U> T and U, each is a union of all possible values ​​and reference types, and the interface is common to all these types (basic System.Object ), If we want to specify T or U , we can add restrictions of the primary and secondary types. In the latest C #, we can limit the class or interface to the most specific. struct or primitive type constraints are not supported.

Currently, we cannot say:

  • where only struct or value type
  • where T if T is a closed type

Example:

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

therefore, we have no way of defining a generic type that can be checked to never break the overlap rule for all types inside T and U Even if we could hold back the structure, you can still get a structure with reference fields, which for some type in the future, T<,> would be wrong.

So what we really ask here is why don't generic types allow implicit type constraints based on code within the class? ; An explicit layout is an internal implementation detail that imposes restrictions on which combinations of T1 and T2 are legal. In my opinion, this is not consistent with the design, depending on type restrictions. It violates the clean type-system contract developed. So why do you even have to face the problem of introducing a type restriction system in design in the first place, if we intend to break it down? We could also throw it away and replace it with exceptions.

With the current state of things:

  • Type constraints are visible public type metadata of a generic type
  • The general type check Foo<T,U> is performed by the open definition of F<,> once. For each instance of the associated type, Foo<t1,u1> , t1 and u1 are checked for the correctness of the type with respect to the constraints. There is no need to re-read the code for the class and methods for Foo<t1,u1> .

All this "As far as I know"

There is no hard technical reason why each instance of a typical type cannot be semantically analyzed for correctness (this indicates C ++), but it seems to violate the design.

TL; DR

Without violating or supplementing the existing type of restriction, there is no possibility of verification.

Perhaps in combination with the corresponding new type restrictions, we can see this in the future.

+4
source share

It is specified in ECMA 335 (CLI), section II, section II.10.1.2:

Explicit: the location of the fields is explicitly specified (§II.10.7). However, the generic type should not have an explicit layout.

You can imagine how this can be inconvenient - given that the size of the type parameter depends on the type parameter, you can get some obviously odd effects ... the reference field is not allowed to overlap with the built-in value type or other link, for example, which will be difficult guarantee as soon as unknown sizes are detected. (I did not consider how this works for 32-bit and 64-bit links, which have a similar but slightly different problem ...)

I suspect that the specification could have been written in order to make some more detailed restrictions, but to make it a simple protective blanket for all generic types is much simpler.

+9
source share

The design of the .NET platform makes certain assumptions about common types, which makes it impossible to make the structure of the explicit layout have any fields, the size of which can vary depending on the parameters of the type type. Perhaps the most fundamental:

  • If there is any combination of type arguments for which the definition of a generic type is valid, it can be considered valid for all combinations of arguments that satisfy the specified restrictions.

Therefore, it would be impossible for .NET to allow an explicit layout structure to use a generic type that was not bound to the class as the type of any field except the very last. In addition, the use of a generic value type that used a generic type as an argument should be limited, in the same form as the passed type itself.

I don’t think there would be any particular problem allowing the explicit layout structure to have a parameter of a universal type with a class restriction if no one were allowed to overlay any field of this type or any type of value that used it as a type argument, and such a field, if it is not known, has a reference type, should have been the last in the structure.

On the other hand, most “safe” use cases could be handled better by having an implicit layout structure containing a common type and having one or more explicit layout structures located inside it. Such an approach could semantically do everything that could be done with explicit layout structures. The only annoyance would be to add an additional level of indirection in the source code when accessing nested members, and for this it would be impossible to allow general structures of the explicit layout, but rather provide a means by which a structure containing another structure can create aliases for members of the internal structure.

As an example:

 [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; } 

Here, the general structure contains a 64-bit value, which is superimposed on two 32-bit values, but it should be verifiable, since the explicitly laid out part has nothing in common.

+2
source share

All Articles