How is it that a struct containing ValueTuple can

2020-08-13 05:10发布

问题:

Consider the following types:

  • (int, int) → managed.
  • struct MyStruct { public (int,int) Value; } → unmanaged!

Problem: A non-generic structure MyStruct, which has a managed member (int,int) has been evaluated as managed type.

Expected Behavior: A structure which contains a managed member, should be considered as managed, the same way the struct MyStruct { int? Value; } are considered as managed.

It seems both types are behaving against the documentations [1] and [2].

Example 1 - unmanaged Constraint

class Program
{
    static void DoSomething<T>() where T : unmanaged { }
    struct MyStruct {  public (int, int) Value; }
    static void Main(string[] args)
    {
        DoSomething<MyStruct>();    // → OK
        DoSomething<(int, int)>();  // → Shows compile-time error
    }
}

Error CS8377 The type '(int, int)' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as parameter 'T' in the generic type or method 'Program.DoSomething()'

Example 2 - pointer or sizeof

Using above structure, the behavior is the same for pointers or sizeof operator:

unsafe 
{
    (int, int)* p1;  // → Compile-time error, 
    MyStruct* p2;    // → Compiles
}

Error CS0208 Cannot take the address of, get the size of, or declare a pointer to a managed type('(int, int)')

Question

  1. How do a struct containing ValueTuple is considered as unmanaged and can satisfy unmanaged constraint while the ValueTuple is considered as managed?

  2. How a struct having ValueTupple<T1, T2> and a struct containing Nullable<T> are treated differently?


Note 1: IMO the issue is different from the Proposal: Unmanaged constructed types (addressed by DavidG in comments), because MyStruct is not generic, on the other hand while int? and (int,int) both are managed, but struct MyStruct { int? Value; } and struct MyStruct { (int, int) Value; } evaluated differently.

回答1:

Thanks for reporting. This is just a bug in the compiler. The tuple when used as a field should be registering as a generic type and hence invalid in an unmanaged type. It appears to be evaluating as a tulpe instead and missing this check.

Good news is that in C# 8.0 this restriction will be going away. The type (int, int) is a valid unmanaged type.