2

When using the unsafe or fixed keyword in C#, you can define pointers to unmanaged types, like byte* int* etc. You can also define a pointer to any struct that only contains unmanaged types, for example:

namespace a
{
   struct MyStruct 
   {
     int value1;
     int value2;
   }

   class b<T>
   {
      unsafe void SomeMethod()
      {
        MyStruct* ptr;
      }
   }
}

However, if the struct is defined within a generic class definition, I get error CS0208: Cannot take the address of, get the size of, or declare a pointer to a managed type. What is the a reason for this restriction?

UPDATE: This error only occurs if the containing class is a generic. I still see no reason for the error - the compiler can see that the struct will always contain unmanaged types, as it doesn't reference the generic type T.

namespace a
{   
    class b<T>
    {
        struct MyStruct 
        {
            int value1;
            int value2;
        }

        unsafe void SomeMethod()
        {
            MyStruct* ptr; // gives a compiler error
        }
    }
}

NOTE: It seems like this feature is being added to C# in an eventual version: see this issue on GitHub.

Reinstate Monica
  • 588
  • 7
  • 21
  • @MachineLearning. nope. All the fields here a value types - that answer refers to `string` which isn't. – Reinstate Monica Sep 03 '16 at 23:59
  • Please provide a good [mcve] that reliably reproduces the error you describe. There are a number of scenarios that can produce that error, but none that I'm aware of that would start with the code you've shown here. – Peter Duniho Sep 04 '16 at 00:03
  • @afuna the int in struct can be unassigned so I guess its value will be in the heap when assigned and thus subject to GC like a string in class.. –  Sep 04 '16 at 00:51
  • @Machine: _"the int in struct can be unassigned so I guess its value will be in the heap"_ -- that statement makes no sense. Value types may or may not be in the heap, depending on where they are located (e.g. a field in a class vs. a local variable), but whether a value has been assigned or not makes no difference in its storage location or whether memory from the heap has been allocated. – Peter Duniho Sep 04 '16 at 01:12
  • @Peter Duniho "field in a class vs local variable" a local variable of value type (int) is in the stack and it's no problem for GC. The same if you have an internal class with int, still ok. They're in the stack. Internal class with string is ko since it's managed by GC. What about an internal class with a nullable int? If it's not permitted, my guess is that it's like an internal struct with a int that can be unassigned –  Sep 04 '16 at 01:49
  • @Machine: first of all, no one ever said _anything_ about a "nullable int" here. Secondly, `Nullable` is a value type just like `int` or any user-defined `struct` and works exactly the same storage-wise. And you are conflating the type semantics (value type vs reference type) with storage (stack vs heap). The two concepts are really almost entirely unrelated, in spite of their interaction with each other. – Peter Duniho Sep 04 '16 at 02:10
  • "Boxing is used to store value types in the garbage-collected heap" that's what I mean is happening and possibly causing the issue. But I surrender, I can't insist. That was my guess, maybe I'm wrong –  Sep 04 '16 at 02:35
  • @Machine: `Nullable` doesn't involve boxing at all, nor is there any boxing in the code posted in the question. Yes, a boxed value type is store in the heap, but then it's no longer a value type; it's a reference type that _contains_ a value type. – Peter Duniho Sep 04 '16 at 03:29
  • @PeterDuniho - thanks for the suggestion for a MCCV - it helped me narrow it down to generics. But I still don't see a reason for the error. – Reinstate Monica Sep 04 '16 at 05:07
  • 1
    Well, you are right, no actual need to reject this code. But Eric Lippert always has the perfect excuse when corner-cases in the C# compiler and language are the subject, this requires *extra* code to double-check that the struct members don't use the type argument. Extra code that has to be written, maintained and documented. Given that the workaround is so simple and the lost feature so modest and the likelihood that a programmer is going to be inconvenienced so low, it is going to be pretty difficult to get them to add this feature. You can certainly try. – Hans Passant Sep 04 '16 at 08:43
  • **Before** the question was edited with generics, using int? value1; would have given the **exact same** error. I made a reasonable, valid guess but I could not foresee the future –  Sep 04 '16 at 13:31

1 Answers1

0

I've edited your code example so that it can actually reproduce the error.

The issue here is that, while the struct appears to be a legal unmanaged type, by nesting it in a generic type, it becomes a "constructed type", which is considered to be a managed type. This is because the full type of your struct actually includes the type parameter and generic types are always managed types. I.e. the type isn't just MyStruct, but rather a.b<T>.MyStruct where T is some type.

From the C# 5 language specification, "10.3.8.6 Nested types in generic classes":

Every type declaration contained within a generic class declaration is implicitly a generic type declaration.

"4.4 Constructed types" reads:

A type-name might identify a constructed type even though it doesn’t specify type parameters directly. This can occur where a type is nested within a generic class declaration, and the instance type of the containing declaration is implicitly used for name lookup…In unsafe code, a constructed type cannot be used as an unmanaged-type.

And from "18.2 Pointer types":

…the referent type of a pointer must be an unmanaged-type. An unmanaged-type is any type that isn’t a reference-type or constructed type, and doesn’t contain reference-type or constructed type fields at any level of nesting.

In other words, the language specification makes it clear both that MyStruct is a "constructed type", and that you aren't allowed to have pointers to constructed types.

As for why the specification makes these restrictions, I'm not the language designer and so I can't provide a definitive answer on that. However, to me it seems safe to assume that the main issue here is that for a constructed type, it is theoretically possible for the type to not be verifiable at compile type as being safe for unsafe code.

In your example, the type parameter T is not used in MyStruct. But it could be, and that would be obviously bad in the unsafe pointer context.

I intuitively would guess that it's theoretically possible for the compiler to do additional analysis to verify MyStruct can be treated as a strictly unmanaged type, but a) I could easily be wrong about that (language designers and compiler writers know a lot more about what could go wrong in situations like this than I would), and b) even if it's theoretically possible, it would be an additional and significant complication in the language specification and the writing of any C# compiler.

That latter point is IMHO justification enough for the language designers to just rule it out. After all, many if not most types nested in a generic type would be using the generic type parameter anyway, so the usefulness of such additional analysis and leniency is probably limited.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • 1
    Thanks for the edit. I had copied and pasted and meant to nest the struct, but must have messed it up. About your answer, I'd agree that it's technically true as per the spec. With regards to the rational, I find it hard to accept - the compiler always checks the struct *anyways* to make sure its fields are unmanaged - it would be easier to just classify a field of a generic type as managed, and when not present not to throw an error. – Reinstate Monica Sep 04 '16 at 17:32
  • _"With regards to the rational, I find it hard to accept"_ -- are you a professional language designer? The "rationale" you are questioning is that of the people who designed C#, extremely competent experts in the field. I think it's very likely that they have a very good reason to make any type inside a generic type to itself be considered "constructed". That said, if you want to argue rationale, you need to argue it with the people who designed the language, not me. Personally, I give them the benefit of the doubt, and assume if the spec says something, there was a good reason for it. – Peter Duniho Sep 04 '16 at 17:41
  • Of course - they might have a great reason. I was referring to the rational the *you* suggested - that it's a complication in the language spec - that's what I find hard to accept, for the reason that I wrote in the comment. I appreciate that you proffered a rational although you are not the designer, and I see no problem in discussing the said rational with *you*. – Reinstate Monica Sep 04 '16 at 18:00
  • @afuna: if you think it's so simple to adjust the language specification to address this particular corner case, I encourage you to do so. You may find it easier to accept that it's not worthwhile, after having done the exercise. – Peter Duniho Sep 05 '16 at 00:46
  • @afuna: I don't have any real basis for discussing it myself, because all I've done is to give the language designers the benefit of the doubt and assume that they knew what they were doing when they did this. I don't have the time or need to second-guess this type of decision, so I don't. But similarly, I have neither the time nor need to defend the decision, except to give the language designers the benefit of the doubt. I'm sorry you didn't find my answer useful. – Peter Duniho Sep 05 '16 at 00:46