为什么通用类型不能有明确的布局?

时间:2014-11-04 22:22:49

标签: c# .net generics clr unsafe

如果尝试使用[StructLayout(LayoutKind.Explicit)]属性创建泛型结构,则使用该结构会在运行时生成异常:

  

System.TypeLoadException:无法加载类型' foo'从汇编' bar'因为泛型类型不能有明确的布局。

我一直很难找到任何证据表明这种限制甚至存在。 Type.IsExplicitLayout文档强烈暗示允许和支持它。有谁知道为什么不允许这样做?我无法想到为什么泛型类型会降低其可验证性。这让我觉得它只是一个他们根本不愿意实施的边缘案例。

这里有an example明确通用布局有用的原因:

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;

修改

要清楚,请注意,即使结构不是通用的,也不会在编译时验证布局。 CLR在运行时检测到引用重叠和x64差异:http://pastebin.com/4RZ6dZ3S 我想问为什么在运行时以任何一种方式完成检查时,泛型都受到限制。

3 个答案:

答案 0 :(得分:9)

在ECMA 335(CLI),分区II,第II.10.1.2节中指定:

  

显式:明确提供字段的布局(§II.10.7)。但是,泛型类型不应具有明确的布局。

你可以想象它是如何尴尬的 - 假设类型参数的大小取决于类型参数,你可能会得到一些明显奇怪的效果......参考字段不允许与内置重叠例如,值类型或其他参考,一旦涉及未知大小就很难保证。 (我没有研究过32位与64位引用的工作原理,它们有类似但略有不同的问题......)

我怀疑可能已经编写了规范来制定更详细的限制 - 但是对所有泛型类型进行简单的全面限制是相当简单的。

答案 1 :(得分:4)

问题的根源是通用性和可验证性,以及基于类型约束的设计。我们不能将引用(指针)与值类型重叠的规则是隐式的多参数约束。所以,我们知道CLR足够聪明,可以在非通用情况下验证这一点......为什么不通用?听起来很有吸引力。

正确的泛型类型定义是可以验证的,适用于任何存在的类型(在约束内)以及将来定义的任何类型。 [1] CLR通过C#,Richter 编译器自己验证开放泛型类型定义,考虑您指定的任何类型约束来缩小可能的类型参数。

如果没有更具体的类型约束,对于Foo<T,U>,T和U分别表示所有可能值和引用类型的并集,以及所有这些类型共有的interface(基数) System.Object)。如果我们想要使T或U更具体,我们可以添加主要和次要类型约束。在最新版本的C#中,我们可以约束的最具体的是类或接口。不支持struct或基本类型约束。

我们目前无法说:

  1. 只有structvalue type
  2. 其中T如果T是密封型
  3. 前:

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

    因此我们无法定义一个可验证的泛型类型,从不违反TU中所有类型的重叠规则。即使我们可以通过struct约束,您仍然可以使用引用字段派生结构,以便将来某些类型T<,>不正确。

    所以我们在这里真正要求的是why don't generic types allow implicit type constraints based on code within the class?;显式布局是一个内部实现细节,对T1T2的哪些组合合法施加限制。在我看来,这与依赖于类型约束的设计不一致。它违反了设计的通用类型系统的清洁合同。那么,如果我们打算打破它,为什么甚至要首先在设计中强加一个类型约束系统呢?我们不妨把它扔掉,换成例外。

    目前的状况:

    1. 类型约束是开放泛型类型的可见元数据
    2. 对开放式定义Foo<T,U>执行一次通用类型F<,>的验证。对于Foo<t1,u1>的每个绑定类型实例,检查t1和u1对约束的类型正确性。无需为Foo<t1,u1>
    3. 的类和方法重新验证代码

      所有这些都是“据我所知”

      没有严格的技术原因可以解释为什么每个泛型类型实例化都无法在语义上进行正确分析(C ++就是证据),但它似乎会破坏设计。

      <强> TL; DR

      在不破坏或补充现有类型约束设计的情况下,无法对此进行验证。

      或许,结合适当的新类型约束,我们可能会在将来看到它。

答案 2 :(得分:2)

.NET框架的设计使得对泛型类型的某些假设将使得基本上不可能允许显式布局结构具有其大小可能基于泛型类型参数而变化的任何字段。也许最根本的是:

  • 如果存在泛型类型定义有效的任何类型参数组合,则可以假定它对满足其指定约束的所有参数组合都有效。

因此,.NET不可能允许显式布局结构使用不限于class的传入泛型类型作为除最后一个字段之外的任何字段的类型。此外,使用传入泛型类型作为参数的泛型值类型的使用必须与传入类型本身相同。

如果允许没有允许显式布局结构具有类约束泛型类型参数,那么我认为不存在任何特殊问题该类型或任何使用它作为类型参数的值类型的任何字段,如果不知道它是引用类型,则这样的字段必须是结构中的最后一个字段。

另一方面,通过使用包含泛型类型的非显式布局结构,并且在其中嵌套一个或多个显式布局结构,可以更好地处理大多数“安全”用例。这种方法可以在语义上完成可以使用显式布局结构完成的所有操作。唯一的烦恼是在访问嵌套成员时必须在源代码中添加额外级别的间接,并且对此的补救不是允许通用显式布局结构,而是提供包含结构的方法另一个结构可以为内部结构成员创建别名。

举个例子:

[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位值上,但它应该是可验证的,因为明确布局的部分没有任何通用。

相关问题