Question

C# doesn't allow structs to derive from classes, but all ValueTypes derive from Object. Where is this distinction made?

How does the CLR handle this?

Was it helpful?

Solution

C# doesn't allow structs to derive from classes

Your statement is incorrect, hence your confusion. C# does allow structs to derive from classes. All structs derive from the same class, System.ValueType, which derives from System.Object. And all enums derive from System.Enum.

UPDATE: There has been some confusion in some (now deleted) comments, which warrants clarification. I'll ask some additional questions:

Do structs derive from a base type?

Plainly yes. We can see this by reading the first page of the specification:

All C# types, including primitive types such as int and double, inherit from a single root object type.

Now, I note that the specification overstates the case here. Pointer types do not derive from object, and the derivation relationship for interface types and type parameter types is more complex than this sketch indicates. However, plainly it is the case that all struct types derive from a base type.

Are there other ways that we know that struct types derive from a base type?

Sure. A struct type can override ToString. What is it overriding, if not a virtual method of its base type? Therefore it must have a base type. That base type is a class.

May I derive a user-defined struct from a class of my choice?

Plainly no. This does not imply that structs do not derive from a class. Structs derive from a class, and thereby inherit the heritable members of that class. In fact, structs are required to derive from a specific class: Enums are required to derive from Enum, structs are required to derive from ValueType. Because these are required, the C# language forbids you from stating the derivation relationship in code.

Why forbid it?

When a relationship is required, the language designer has options: (1) require the user to type the required incantation, (2) make it optional, or (3) forbid it. Each has pros and cons, and the C# language designers have chosen differently depending on the specific details of each.

For example, const fields are required to be static, but it is forbidden to say that they are because doing so is first, pointless verbiage, and second, implies that there are non-static const fields. But overloaded operators are required to be marked as static, even though the developer has no choice; it is too easy for developers to believe that an operator overload is an instance method otherwise. This overrides the concern that a user may come to believe that the "static" implies that, say "virtual" is also a possibility.

In this case, requiring a user to say that their struct derives from ValueType seems like mere excess verbiage, and it implies that the struct could derive from another type. To eliminate both these problems, C# makes it illegal to state in the code that a struct derives from a base type, though plainly it does.

Similarly all delegate types derive from MulticastDelegate, but C# requires you to not say that.

So, now we have established that all structs in C# derive from a class.

What is the relationship between inheritance and derivation from a class?

Many people are confused by the inheritance relationship in C#. The inheritance relationship is quite straightforward: if a struct, class or delegate type D derives from a class type B then the heritable members of B are also members of D. It's as simple as that.

What does it mean with regards to inheritance when we say that a struct derives from ValueType? Simply that all the heritable members of ValueType are also members of the struct. This is how structs obtain their implementation of ToString, for example; it is inherited from the base class of the struct.

All heritable members? Surely not. Are private members heritable?

Yes. All private members of a base class are also members of the derived type. It is illegal to call those members by name of course if the call site is not in the accessibility domain of the member. Just because you have a member does not mean you can use it!

We now continue with the original answer:


How does the CLR handle this?

Extremely well. :-)

What makes a value type a value type is that its instances are copied by value. What makes a reference type a reference type is that its instances are copied by reference. You seem to have some belief that the inheritance relationship between value types and reference types is somehow special and unusual, but I don't understand what that belief is. Inheritance has nothing to do with how things are copied.

Look at it this way. Suppose I told you the following facts:

  • There are two kinds of boxes, red boxes and blue boxes.

  • Every red box is empty.

  • There are three special blue boxes called O, V and E.

  • O is not inside any box.

  • V is inside O.

  • E is inside V.

  • No other blue box is inside V.

  • No blue box is inside E.

  • Every red box is in either V or E.

  • Every blue box other than O is itself inside a blue box.

The blue boxes are reference types, the red boxes are value types, O is System.Object, V is System.ValueType, E is System.Enum, and the "inside" relationship is "derives from".

That's a perfectly consistent and straightforward set of rules which you could easily implement yourself, if you had a lot of cardboard and a lot of patience. Whether a box is red or blue has nothing to do with what it's inside; in the real world it is perfectly possible to put a red box inside a blue box. In the CLR, it is perfectly legal to make a value type that inherits from a reference type, so long as it is either System.ValueType or System.Enum.

So let's rephrase your question:

How do ValueTypes derive from Object (ReferenceType) and still be ValueTypes?

as

How is it possible that every red box (value types) is inside (derives from) box O (System.Object), which is a blue box (a reference Type) and still be a red box (a value type)?

When you phrase it like that, I hope it's obvious. There's nothing stopping you from putting a red box inside box V, which is inside box O, which is blue. Why would there be?


AN ADDITIONAL UPDATE:

Joan's original question was about how it is possible that a value type derives from a reference type. My original answer did not really explain any of the mechanisms that the CLR uses to account for the fact that we have a derivation relationship between two things that have completely different representations -- namely, whether the referred-to data has an object header, a sync block, whether it owns its own storage for the purposes of garbage collection, and so on. These mechanisms are complicated, too complicated to explain in one answer. The rules of the CLR type system are quite a bit more complex than the somewhat simplified flavour of it that we see in C#, where there is not a strong distinction made between the boxed and unboxed versions of a type, for example. The introduction of generics also caused a great deal of additional complexity to be added to the CLR. Consult the CLI specification for details, paying particular attention to the rules for boxing and constrained virtual calls.

OTHER TIPS

This is a somewhat artificial construct maintained by the CLR in order to allow all types to be treated as a System.Object.

Value types derive from System.Object through System.ValueType, which is where the special handling occurs (ie: the CLR handles boxing/unboxing, etc for any type deriving from ValueType).

Small correction, C# doesn't allow structs to custom derive from anything, not just classes. All a struct can do is implement an interface which is very different from derivation.

I think the best way to answer this is that ValueType is special. It is essentially the base class for all value types in the CLR type system. It's hard to know how to answer "how does the CLR handles this" because it's simply a rule of the CLR.

Your statement is incorrect, hence your confusion. C# does allow structs to derive from classes. All structs derive from the same class, System.ValueType

So let's try this:

 struct MyStruct :  System.ValueType
 {
 }

This will not even compile. Compiler will remind you "Type 'System.ValueType' in interface list is not an interface".

When decompile Int32 which is a struct, you will find :

public struct Int32 : IComparable, IFormattable, IConvertible {}, not mentionning it is derived from System.ValueType. But in object browser, you do find Int32 does inherit from System.ValueType.

So all these lead me to believe:

I think the best way to answer this is that ValueType is special. It is essentially the base class for all value types in the CLR type system. It's hard to know how to answer "how does the CLR handles this" because it's simply a rule of the CLR.

A boxed value type is effectively a reference type (it walks like one and quacks like one, so effectively it is one). I would suggest that ValueType isn't really the base type of value types, but rather is the base reference type to which value types can be converted when cast to type Object. Non-boxed value types themselves are outside the object hierarchy.

Rationale

Of all the answers, @supercat's answer comes closest to the actual answer. Since the other answers don't really answer the question, and downright make incorrect claims (for example that value types inherit from anything), I decided to answer the question.

 

Prologue

This answer is based on my own reverse engineering and the CLI specification.

struct and class are C# keywords. As far as the CLI is concerned, all types (classes, interfaces, structs, etc.) are defined by class definitions.

For example, an object type (Known in C# as class) is defined as follows:

.class MyClass
{
}

 

An interface is defined by a class definition with the interface semantic attribute:

.class interface MyInterface
{
}

 

What about value types?

The reason that structs can inherit from System.ValueType and still be value types, is because.. they don't.

Value types are simple data structures. Value types do not inherit from anything and they cannot implement interfaces. Value types are not subtypes of any type, and they do not have any type information. Given a memory address of a value type, it's not possible to identify what the value type represents, unlike a reference type which has type information in a hidden field.

If we imagine the following C# struct:

namespace MyNamespace
{
    struct MyValueType : ICloneable
    {
        public int A;
        public int B;
        public int C;

        public object Clone()
        {
            // body omitted
        }
    }
}

The following is the class definition of that struct:

.class MyNamespace.MyValueType extends [mscorlib]System.ValueType implements [mscorlib]System.ICloneable
{
    .field public int32 A;
    .field public int32 B;
    .field public int32 C;

    .method public final hidebysig newslot virtual instance object Clone() cil managed
    {
        // body omitted
    }
}

So what's going on here? It clearly extends System.ValueType, which is an object/reference type, and implements System.ICloneable.

The explanation is, that when a class definition extends System.ValueType it actually defines 2 things: A value type, and the value type's corresponding boxed type. The members of the class definition define the representation for both the value type and the corresponding boxed type. It is not the value type that extends and implements, it's the corresponding boxed type that does. The extends and implements keywords only apply to the boxed type.

To clarify, the class definition above does 2 things:

  1. Defines a value type with 3 fields (And one method). It does not inherit from anything, and it does not implement any interfaces (value types can do neither).
  2. Defines an object type (the boxed type) with 3 fields (And implemeting one interface method), inheriting from System.ValueType, and implementing the System.ICloneable interface.

Note also, that any class definition extending System.ValueType is also intrinsically sealed, whether the sealed keyword is specified or not.

Since value types are just simple structures, don't inherit, don't implement and don't support polymorphism, they can't be used with the rest of the type system. To work around this, on top of the value type, the CLR also defines a corresponding reference type with the same fields, known as the boxed type. So while a value type can't be passed around to methods taking an object, its corresponding boxed type can.

 

Now, if you were to define a method in C# like

public static void BlaBla(MyNamespace.MyValueType x),

you know that the method will take the value type MyNamespace.MyValueType.

Above, we learned that the class definition that results from the struct keyword in C# actually defines both a value type and an object type. In C#, we can only refer to the defined value type. However, this is only a limitation of C#. In IL we can actually refer to both.

When refering to types in IL, a couple of constraints are supported, among which are class and valuetype. If we use valuetype MyNamespace.MyValueType we're constraining the type to the value type of the class definition. Likewise, we can use class MyNamespace.MyValueType to constrain the type to the boxed type of the class definition.

That is, .method static void Print(valuetype MyNamespace.MyValueType test) cil managed takes the value type defined by the MyNamespace.MyValueType class definition,

while .method static void Print(class MyNamespace.MyValueType test) cil managed takes the boxed type defined by the MyNamespace.MyValueType class definition.

So you can basically instantiate and use the corresponding boxed type throughout your IL program, as if it was defined like a C# class. This would initialize the valuetype

newobj void valuetype MyNamespace.MyValueType::.ctor()

The runtime will refuse to instantiate the boxed type through newobj like

newobj void class MyNamespace.MyValueType::.ctor()

However, the box instruction will instantiate the boxed type of the value type and copy the values from the value type into it. That will get you an instance of the boxed type that you can store in any System.ValueType, object or class MyNamespace.MyValueType variable, and pass around to any method that takes a System.ValueType, object or class MyNamespace.MyValueType as an argument, and it will, for all intents and purposes, work like any other reference type. It is NOT a value type, but the corresponding boxed type of a value type.

 

Example

Below is a small program I have written in IL to demonstrate this. I've commented it throughout. Pay special attention to the use of the valuetype and class keywords.

// Class definition for a class.
.class MyNamespace.Program
{
    // Entry point method definition.
    .method static void Main() cil managed
    {
        .entrypoint // This is the entry point of the application.
        .maxstack 8
        .locals init
        (
            [0] valuetype MyNamespace.MyValueType, // Local variable able to hold the value type of the MyNamespace.MyValueType class definition.
            [1] class MyNamespace.MyValueType // Local variable able to hold the boxed type of the MyNamespace.MyValueType class definition.
        )

        ldloca.s 0 // Load the address of local variable at index 0 onto the evaluation stack.
        initobj MyNamespace.MyValueType // Set all fields of our value type to 0 (required by the CLI for the assembly to be verifiable).

        // The following sets fields A, B and C to 1, 2 and 3 in the value type, respectively.
        ldloca.s 0
        ldc.i4 1
        stfld int32 MyNamespace.MyValueType::A
        ldloca.s 0
        ldc.i4 2
        stfld int32 MyNamespace.MyValueType::B
        ldloca.s 0
        ldc.i4 3
        stfld int32 MyNamespace.MyValueType::C

        ldloc.0 // Load a copy of our value type onto the evaluation stack as an argument for the call below.
        call void MyNamespace.Program::Print(valuetype MyNamespace.MyValueType) // Call the overload of Print() that takes a value type.

        ldloc.0 // Load a copy of our value type onto the evaluation stack.
        box MyNamespace.MyValueType // Create the corresponding boxed type of our value type.
        stloc.1 // Store it in the local variable that takes the boxed version of our value type.
        ldloc.1 // Load it back onto the evaluation stack.
        call void MyNamespace.Program::Print(class MyNamespace.MyValueType) // Call the overload of Print() that takes a reference type.
        ret // Return.
    }

    // This method takes the value type of our MyNamespace.MyValueType class definition.
    // This is equivalent to "static void Print(MyNamespace.MyValueType test)" in C#.
    .method static void Print(valuetype MyNamespace.MyValueType test) cil managed
    {
        .maxstack 8
        // Equivalent to "Console.WriteLine(test.ToString()); test.PrintA();"
        ldarga.s test
        constrained. MyNamespace.MyValueType // callvirt prefix that will box 'test' for the ToString() call below. (Remember, 'test' is a value type, which has no ToString() method, but its corresponding boxed type does)
        callvirt instance string [mscorlib]System.Object::ToString()
        call void [mscorlib]System.Console::WriteLine(string)
        ldarga.s test
        call instance string MyNamespace.MyValueType::PrintA()
        ret
    }

    // This method takes the boxed type of our MyNamespace.MyValueType class definition.
    // This is not possible in C#.
    // The closest you can get to this, is to accept the parent class System.ValueType or System.Object,
    // which of course will allow callers to pass ANY boxed value type or object, and won't allow you to call PrintB() directly.
    // This method will only allow the boxed type of this specific value type.
    .method static void Print(class MyNamespace.MyValueType test) cil managed noinlining
    {
        .maxstack 8
        .locals init
        (
            [0] valuetype MyNamespace.MyValueType // Local variable for unboxing operation below.
        )

        ldarg.0
        callvirt instance string [mscorlib]System.Object::ToString() // No 'constrained' prefix to box necessary, since 'test' is already a reference type.
        call void [mscorlib]System.Console::WriteLine(string)

        // Now, methods in value types operate on the value type, not the boxed type,
        // so even though we can call PrintB() directly because 'test' is a class MyNamespace.MyValueType and not a class System.Object,
        // we have to unbox it and call PrintB() on the unboxed type.
        // (without unboxing, the call will still succeed, but since PrintB() expects the value type and not the boxed type, it will read the wrong field offset
        // (because offset 0 of an object is the object header pointer, and offset 0 of a value type is its first field))

        // I'll call PrintB() twice to show two different ways.
        // This first one unboxes, then passes the resulting value type (which is a copy) to PrintB().
        ldarg.0 // Push our MyNamespace.MyValueType boxed type instance onto the evaluation stack.
        unbox.any MyNamespace.MyValueType // Unboxes and pushes a copy of the value type onto the evaluation stack.
        stloc.0 // Store in local variable.
        ldloca.s 0 // Pass the address of the local variable to PrintB() (for the 'this' pointer).
        call instance void MyNamespace.MyValueType::PrintB()

        // Now, the above is a full unboxing, so PrintB() receives a copy of 'test'. So if PrintB() were to make changes to the boxed type, it would be on the copy,
        // not the instance that was passed to Print().
        // This can be fixed by using the 'unbox' instruction instead of the 'unbox.any' instruction.
        // 'unbox.any' will calculate the address of the value type part of the boxed type and then copy the data and push it onto the evaluation stack.
        // 'unbox' will just calculate the address of the value type part of the boxed type and push the address onto the evaluation stack.
        // Using just unbox, PrintB will get a pointer to the value type part of the boxed type (object address + size of object header pointer), and thus perform its operations on the actual boxed type instance.
        ldarg.0
        unbox MyNamespace.MyValueType
        call instance void MyNamespace.MyValueType::PrintB()

        ret
    }
}

// Class definition for a value type, defining both the value type and the corresponding boxed type.
.class MyNamespace.MyValueType extends [mscorlib]System.ValueType
{
    .field public int32 A;
    .field public int32 B;
    .field public int32 C;

    .method void PrintA() cil managed
    {
        // Equivalent to "Console.WriteLine(this.B.ToString());"
        // Identical to PrintB, so I've put the comments in that.
        ldarg.0
        ldflda int32 MyNamespace.MyValueType::A
        ldstr "x8"
        call instance string [mscorlib]System.Int32::ToString(string)
        call void [mscorlib]System.Console::WriteLine(string)
        ret
    }

    .method void PrintB() cil managed
    {
        // Equivalent to "Console.WriteLine(this.B.ToString());"
        ldarg.0 // Load the value in the 'this' pointer onto the evaluation stack (Load the address of the struct instance).
        ldflda int32 MyNamespace.MyValueType::B  // Load the address of the field 'B' onto the evaluation stack ('this' pointer for ToString() call below).
        ldstr "x8" // Load constant string "x8" onto the evaluation stack (Format specifier).
        call instance string [mscorlib]System.Int32::ToString(string) // Du'h.
        call void [mscorlib]System.Console::WriteLine(string) // Print it.
        ret
    }
}

As you may have noticed, the two Print() methods are overloads and can be, because they don't have the same signature. One takes the value type of the MyNamespace.MyValueType class definition, and the other takes the boxed type of the MyNamespace.MyValueType class definition.

If you were to assemble the code above and decompile it using eg. ILSpy, you'd get some confusing results, even when outputting as IL, because ILSpy (can't comment on other decompilers) will assume the constraint based on the type, so even though one method takes a reference type and the other a value type, both will show up with the valuetype keyword. When decompiling as C#, both overload signatures will appear identical.

Here's the output from ILSpy of the assembled IL code above (the comments are mine):

internal class Program
{
    private static void DoIt()
    {
        MyNamespace.MyValueType myValueType = default(MyNamespace.MyValueType);
        myValueType.A = 1;
        myValueType.B = 2;
        myValueType.C = 3;
        MyNamespace.Program.Print(myValueType); // The overload taking a value type.
        MyNamespace.MyValueType test = (MyNamespace.MyValueType)(object)myValueType; // ILSpy tries to understand the boxing operation as best as it can, but ends up boxing and unboxing, despite the IL only boxing (because C# boxes/unboxes by casting and cannot differentiate between the value type and boxed type).
        MyNamespace.Program.Print(test); // The overload taking the boxed type.
    }

    // The overload taking a value type.
    private static void Print(MyNamespace.MyValueType test)
    {
        Console.WriteLine(test.ToString());
        test.PrintA();
    }

    // The overload taking the boxed type.
    private static void Print(MyNamespace.MyValueType test)
    {
        Console.WriteLine(test.ToString());
        ((MyNamespace.MyValueType)(object)test).PrintB();
        ((MyNamespace.MyValueType)test).PrintB();
    }
}

[StructLayout(LayoutKind.Auto)]
internal struct MyValueType
{
    public int A;
    public int B;
    public int C;

    private void PrintA()
    {
        Console.WriteLine(A.ToString("x8"));
    }

    private void PrintB()
    {
        Console.WriteLine(B.ToString("x8"));
    }
}

Don't confuse the above decompilation to C# as how to do it in C#. It's not possible, and the decompilation is very, very wrong. It's simply to illustrate how ILSpy and other decompilers can be misleading. (Just like they can be misleading when looking at things like System.Int32 that appears to contain itself (while it in fact contains an int32, which is a built-in type, for which System.Int32 is the corresponding CLS type (And like two overloaded methods can take a value type and boxed type of the same class definition, two overloads can take an int32 and System.Int32 and co-exist (and ILSpy will show them both taking 'int'))).

ILDasm will correctly show the valuetype and class constraint keywords.

 

Summary

So, in summary, and to answer the question:

Value types are not reference types and do not inherit from System.ValueType or any other type, and they cannot implement interfaces. The corresponding boxed types that are also defined do inherit from System.ValueType and can implement interfaces.

A .class definition defines different things depending on circumstance.

  • If the interface semantic attribute is specified, the class definition defines an interface.
  • If the interface semantic attribute is not specified, and the definition does not extend System.ValueType, the class definition defines an object type (class).
  • If the interface semantic attribute is not specified, and the definition does extend System.ValueType, the class definition defines a value type and its corresponding boxed type (struct).

Memory layout

This section assumes a 32-bit process

As already mentioned, value types do not have type information, and thus it's not possible to identify what a value type represents from its memory location. A struct describes a simple data type, and contains just the fields it defines:

public struct MyStruct
{
    public int A;
    public short B;
    public int C;
}

If we imagine that an instance of MyStruct was allocated at address 0x1000, then this is the memory layout:

0x1000: int A;
0x1004: short B;
0x1006: 2 byte padding
0x1008: int C;

Structs default to sequential layout. Fields are aligned on boundaries of their own size. Padding is added to satisfy this.

 

If we define a class in the exact same way, as:

public class MyClass
{
    public int A;
    public short B;
    public int C;
}

Imagining the same address, the memory layout is as follows:

0x1000: Pointer to object header
0x1004: int A;
0x1008: int C;
0x100C: short B;
0x100E: 2 byte padding
0x1010: 4 bytes extra

Classes default to automatic layout, and the JIT compiler will arrange them in the most optimal order. Fields are aligned on boundaries of their own size. Padding is added to satisfy this. I'm not sure why, but every class always has an additional 4 bytes at the end.

Offset 0 contains the address of the object header, which contains type information, the virtual method table, etc. This allows the runtime to identify what the data at an address represents, unlike value types.

Thus, value types do not support inheritance, interfaces nor polymorphism.

Methods

Value types do not have virtual method tables, and thus do not support polymorphism. However, their corresponding boxed type does.

When you have an instance of a struct and attempt to call a virtual method like ToString() defined on System.Object, the runtime has to box the struct.

MyStruct myStruct = new MyStruct();
Console.WriteLine(myStruct.ToString()); // ToString() call causes boxing of MyStruct.

However, if the struct overrides ToString() then the call will be statically bound and the runtime will call MyStruct.ToString() without boxing and without looking in any virtual method tables (structs don't have any). For this reason, it's also able to inline the ToString() call.

If the struct overrides ToString() and is boxed, then the call will be resolved using the virtual method table.

System.ValueType myStruct = new MyStruct(); // Creates a new instance of the boxed type of MyStruct.
Console.WriteLine(myStruct.ToString()); // ToString() is now called through the virtual method table.

However, remember that ToString() is defined in the struct, and thus operates on the struct value, so it expects a value type. The boxed type, like any other class, has an object header. If the ToString() method defined on the struct was called directly with the boxed type in the this pointer, when trying to access field A in MyStruct, it would access offset 0, which in the boxed type would be the object header pointer. So the boxed type has a hidden method that does the actual overriding of ToString(). This hidden method unboxes (address calculation only, like 'unbox') the boxed type then statically calls the ToString() defined on the struct.

Likewise, the boxed type has a hidden method for each implemented interface method that does the same unboxing then statically calls the method defined in the struct.

CLI specification

Boxing

I.8.2.4 For every value type, the CTS defines a corresponding reference type called the boxed type. The reverse is not true: In general, reference types do not have a corresponding value type. The representation of a value of a boxed type (a boxed value) is a location where a value of the value type can be stored. A boxed type is an object type and a boxed value is an object.

Defining value types

I.8.9.7 Not all types defined by a class definition are object types (see §I.8.2.3); in particular, value types are not object types, but they are defined using a class definition. A class definition for a value type defines both the (unboxed) value type and the associated boxed type (see §I.8.2.4). The members of the class definition define the representation of both.

II.10.1.3 The type semantic attributes specify whether an interface, class, or value type shall be defined. The interface attribute specifies an interface. If this attribute is not present and the definition extends (directly or indirectly) System.ValueType, and the definition is not for System.Enum, a value type shall be defined (§II.13). Otherwise, a class shall be defined (§II.11).

Value types do not inherit

I.8.9.10 In their unboxed form value types do not inherit from any type. Boxed value types shall inherit directly from System.ValueType unless they are enumerations, in which case, they shall inherit from System.Enum. Boxed value types shall be sealed.

II.13 Unboxed value types are not considered subtypes of another type and it is not valid to use the isinst instruction (see Partition III) on unboxed value types. The isinst instruction can be used for boxed value types, however.

I.8.9.10 A value type does not inherit; rather the base type specified in the class definition defines the base type of the boxed type.

Value types do not implement interfaces

I.8.9.7 Value types do not support interface contracts, but their associated boxed types do.

II.13 Value types shall implement zero or more interfaces, but this has meaning only in their boxed form (§II.13.3).

I.8.2.4 Interfaces and inheritance are defined only on reference types. Thus, while a value type definition (§I.8.9.7) can specify both interfaces that shall be implemented by the value type and the class (System.ValueType or System.Enum) from which it inherits, these apply only to boxed values.

Referring to value types vs boxed types

II.13.1 The unboxed form of a value type shall be referred to by using the valuetype keyword followed by a type reference. The boxed form of a value type shall be referred to by using the boxed keyword followed by a type reference.

Note: The specification is wrong here, there is no boxed keyword. The keyword is class.

Epilogue

I think part of the confusion of how value types seem to inherit, stems from the fact that C# uses casting syntax to perform boxing and unboxing, which makes it seem like you're performing casts, which is not the case. (object)myStruct in C# creates a new instance of the boxed type of the value type; it does not perform any casts. Likewise, (MyStruct)obj in C# unboxes a boxed type, copying the value part out; it does not perform any casts.

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