Question

Nullable<T> is a struct that has the following definition:

public struct Nullable<T> where T : struct

where struct is a type constraint so that T is constrained to (according to the spec §4.4.4):

  • struct type or enum type
  • not a nullable type.

Looking in the source for Nullable<T> there are no special attributes (expect for [Serializable]), so how does the compiler can recognise it as a "nullable type"?

In response to the comments below:

int is an alias for Int32:

(§4.1.4) simple types are identified through reserved words, but these reserved words are simply aliases for predefined struct types in the System namespace

T? is shorthand for Nullable<T>:

(§4.1.10) - A nullable type is written T?, where T is the underlying type. This syntax is shorthand for System.Nullable, and the two forms can be used interchangeably.

This seems to be a distinct difference which isn't reflected below.

So how does the compiler recognise a simple struct (with no special code) as a "nullable type", struct name?

Was it helpful?

Solution

Based off of the Shared Source CLI 2.0, Nullable<T> is made "special" via the PREDEFTYPEDEF macro, which takes the name "System.Nullable" and maps it to the attribute PT_G_OPTIONAL which is checked throughout the rest of the compiler.

With regards to the aliases of intSystem.Int32 et al., see the "nice name" column.

From sscli20\csharp\inc\predeftype.h:

//         id            full type name       required  simple     numer    AggKind  fund type   elementtype,      nice name,    zero, quasi simple numer, attribute arg size serialization type,  predef attribute, arity, in mscorlib)
PREDEFTYPEDEF(PT_BYTE,   "System.Byte",         1,            1,      1,     Struct,   FT_U1,   ELEMENT_TYPE_U1,      L"byte",      0,                 0,      1,      SERIALIZATION_TYPE_U1,      PA_COUNT, 0, 1)
PREDEFTYPEDEF(PT_SHORT,  "System.Int16",        1,            1,      1,     Struct,   FT_I2,   ELEMENT_TYPE_I2,      L"short",     0,                 0,      2,      SERIALIZATION_TYPE_I2,      PA_COUNT, 0, 1)
PREDEFTYPEDEF(PT_INT,    "System.Int32",        1,            1,      1,     Struct,   FT_I4,   ELEMENT_TYPE_I4,      L"int",       0,                 0,      4,      SERIALIZATION_TYPE_I4,      PA_COUNT, 0, 1)
PREDEFTYPEDEF(PT_LONG,   "System.Int64",        1,            1,      1,     Struct,   FT_I8,   ELEMENT_TYPE_I8,      L"long",      &longZero,         0,      8,      SERIALIZATION_TYPE_I8,      PA_COUNT, 0, 1)
// ... snip ...
// Nullable<T>
PREDEFTYPEDEF(PT_G_OPTIONAL, "System.Nullable",  0,     0,     0,   Struct,   FT_STRUCT,  ELEMENT_TYPE_END,      NULL,          0,                 0,      0,      0,                          PA_COUNT, 1, 1)

Then its used elsewhere like this:

From sscli20\csharp\sccomp\nullable.cpp:

/***************************************************************************************************
    Return true iff the method is the nullable ctor taking one parameter.
***************************************************************************************************/
bool FUNCBREC::IsNubCtor(METHSYM * meth)
{
    return meth && meth->getClass()->isPredefAgg(PT_G_OPTIONAL) && meth->params->size == 1 &&
        meth->params->Item(0)->isTYVARSYM() && meth->isCtor();
}

For those reading this question, you are probably the target audience for Shared Source CLI 2.0 Internals, which was released as a free e-book.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top