#646 – Value Types Don’t Have Finalizers

In .NET, you override the Object.Finalize method to dispose of any unmanaged resources when the object is being garbage collected.  In C#, you write this finalizer using the destructor (~) syntax.  You can also implement the Dispose pattern to allow users of your object to deterministically dispose of  resources.

You cannot implement a finalizer or the dispose pattern for a value type.  A finalizer makes no sense for a value type because value typed objects are not garbage collected–they simply die when they go out of scope.

Since the finalizer and the dispose pattern exist for the purpose of releasing unmanaged resources, and you can’t implement either for a value type, you should avoid using unmanaged resources in value types.  A user would have to explicitly call some method to do the cleanup and there is no guarantee that they would remember to call the method.

#576 – Using the is Operator with Value Typed Objects

You can use the is operator to check the type of value-typed objects.  The result of a check using the is operator is true if the type of the expression matches the specified type exactly.  Because value types don’t support inheritance, an object of one type will never return true for the is operator on a different type, even if the value is assignment compatible with the specified type.

bool check;

byte b = 1;
short s = 2;

check = b is byte;     // true
check = b is short;    // false

// Assignment succeeds
s = b;        // short <= byte

check = s is short;    // true
check = s is byte;     // false

Because all value types inherit from System.ValueType and, indirectly, from System.Object, the is operator always returns true when checking against these types.

check = b is object;   // true
check = s is object;   // true
check = b is System.ValueType;   // true
check = s is System.ValueType;   // true

#404 – Equals Method vs. == Operator for Value Types

In C#, you can compare two objects for equality by using the Equality operator (==) or by calling the Equals method.  For built-in value types, the default behavior checks for value equality, or equivalence.

            int i1 = 42;
            int i2 = 42;

            bool check1 = i1 == i2;      // true
            bool check2 = i1.Equals(i2);  // true
            bool check3 = int.Equals(i2, i2);  // true

For the predefined value types (e.g. int, float):

  • The Equals method performs a value equality check
  • The == operator resolves to the CIL ceq instruction, which does a strict identity check

For user-defined struct types:

  • The Equals method implementation performs a value equality check by comparing each field of the struct (using reflection).  You can (and should) override Equals to provide an implementation specific to the struct.
  • The == operator is undefined, unless you override it in the struct.

#327 – Assigning a struct to a New Variable Makes a Copy

Because a struct is a value type, you make a copy of the entire struct when you assign it to a new variable.

Assume that we define the following struct:

public struct MovieInfo
    public string Title;
    public int Year;
    public string Director;

If we define a variable of this type and then assign it to another variable, the second variable gets a copy of all of the data. If we then change some field in the first copy, the second copy is unchanged.

            MovieInfo goneWithWind;
            goneWithWind.Title = "Gone With the Wind";
            goneWithWind.Year = 1938;
            goneWithWind.Director = "Victor Fleming";

            // Actually makes a copy of entire struct
            MovieInfo goodClarkGableFlick = goneWithWind;

            // Fix the year
            goneWithWind.Year = 1939;

            Console.WriteLine(goodClarkGableFlick.Year);    // Oops, still 1938

#218 – Store Value-Typed Objects on the Heap Through Boxing

Value-typed objects are typically stored on the stack, while reference-typed objects are stored on the heap.  You can, however, convert an instance of a value type to a reference type object through a process known as boxing.

In the example below, we box i, assigning it to a reference type variable.  Because i derives from System.Object (object), as do all types in .NET, we can assign i to o without doing a cast.  A new object is created on the heap, the value of i is copied into it, and the variable o is set to reference the new object.

            int i = 46;
            object o = i;   // Box i

Because a copy of the value-typed object is made, you can change the original object without changing the new object on the heap.

            i = 47;   // i now 47, but o still points to object with value of 46

#202 – All Fields in an Object Are Automatically Initialized

When you declare an instance of a value type without initializing it, the compiler prevents you from referencing the uninitialized variable.

            int x;

            Console.WriteLine(x);   // Compile-time error: [Use of unassigned local variable 'x']

If you declare and instantiate a reference type, the internal fields and properties are all initialized by setting all of the bits of the underlying memory for each item to 0.  This equates to:

  • Reference types = null
  • Numeric types = 0
  • Enum types = 0
  • Char type =
  • Boolean type = false

This means that value types declared inside the object are automatically initialized when the object is created.

For example, assume that we create a new Person object without calling a constructor that initializes any fields.

            Person p = new Person();

We can look at the new Person object in the debugger to see that all of its fields have been initialized.

#186 – Value Types on the Heap

Value types are normally allocated on the stack. They can, however, be allocated on the heap–when they are declared within an object.  When an object is instantiated, memory for the entire object, including all of its data, is allocated on the heap.

For example, assume we define a Person class with some properties that are value types (int) and some that are reference types (string).

    public class Person
        string FirstName;
        string LastName;

        int Age;
        int HeightInInches;      // also on heap

        public Person(string firstName, string lastName)
            FirstName = firstName;
            LastName = lastName;

When you create an instance of the Person object,

            Person p = new Person("Zsa Zsa", "Gabor");

the Person object is created on the heap and all of its data members, including the Age and HeightInInches value types, are also on the heap.

#37 – All Value Types Have a Default Constructor

All built-in value types in C# support a default (parameterless) constructor using the new keyword.  The default constructor allows you to instantiate an object such that it takes on a default value.

You’d normally instantiate one of the built-in types by giving  the associated variable a value.  But it’s also possible to use the new keyword to cause the variable to take on a default value.

 int i;          // Not instantiated yet
 int n1 = 12;    // Instanatiated, w/value of 12
 int n2 = new int();   // Instantiated, w/default value

The default values for the built-in value types are:

  • bool type = false
  • Numeric types (e.g. int, float) = 0 or 0.0
  • char type = single empty character
  • DateTime type = 1/1/0001 12:00:00 AM

#31 – Value Types and Reference Types

All types in C#, whether built-in or user-defined custom types, can be categorized as either value types or reference types.

  • Value types
    • Derive from System.ValueType (which derives from System.Object)
    • Allocated on the stack  (unless declared inside a reference type)
    • Value type variable contains value directly
    • Assignment makes a copy of the value
    • Passed by value (a copy is made)
    • Not garbage collected–die when they go out of scope
    • Either struct or enum
    • Sealed–can’t inherit from them
  • Reference types
    • Derive from System.Object or another reference type
    • Allocated on the heap
    • Reference type variable contains a reference (pointer) to the object’s contents (or contains null)
    • Assignment creates a new reference to the original object
    • Passed by reference  (pointer to object is passed)
    • Garbage collected
    • One of: class, delegate, array or interface
    • Support inheritance