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

DBN*_*DBN 9 .net c# generics clr unsafe

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

System.TypeLoadException:无法从程序集"bar"加载类型'foo',因为泛型类型不能具有显式布局.

我一直很难找到任何证据证明这种限制甚至存在.该Type.IsExplicitLayout文档强烈暗示,它被允许和支持.有谁知道为什么不允许这样做?我想不出为什么泛型类型会使其不易验证的任何原因.作为一个边缘案例,它让我感到震惊,他们根本不愿意实施.

以下是显式通用布局有用的原因示例:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

TaggedUnion<int, double> foo = 1;
Debug.Assert(foo.GetUnionType() == typeof(int));
foo = 1.0;
Debug.Assert(foo.GetUnionType() == typeof(double));
double bar = (double) foo;
Run Code Online (Sandbox Code Playgroud)

编辑:

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

Jon*_*eet 9

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

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

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

我怀疑可能已经编写了规范以进行更详细的限制 - 但是对所有泛型类型进行简单的全局限制要简单得多.


cod*_*eim 5

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

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

在没有更具体的类型约束的情况下, for Foo<T,U>、 T 和 U 每个都表示所有可能的值和引用类型的联合,以及interface所有这些类型的共同点(基System.Object)。如果我们想让 T 或 U 更具体,我们可以添加主要和次要类型约束。在最新版本的 C# 中,我们可以约束的最具体的是类或接口。不支持结构或原始类型约束。

我们目前不能说:

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

前任:

public struct TaggedUnion<T1, T2>
    where T1 : SealedThing   // illegal
Run Code Online (Sandbox Code Playgroud)

所以我们没有定义一个通用的类型,它是可验证永远不会违背对所有类型中的重叠规则的方式TU。即使我们可以通过 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; 博士

在不打破或补充现有类型约束设计的情况下,这是无法验证的。

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