#187 – Everything Is an Object

In C#, every data item is an object, created on the stack or the heap.  Even built-in data types, like double or int represent classes (System.Double and System.Int32) and declaring an object to be of one of these types is equivalent to instantiating an object of the appropriate type.

            // Declare and instantiate an int
            int i = 12;

            // Equivalent to
            System.Int32 i2 = new System.Int32();
            i2 = 12;

Similarly, every function call involves calling a method declared in some type–either a static or an instance method.

Even constants represent instances of some object.

Advertisement

#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.

#185 – The Heap and the Stack

In C#, all objects are created on either the heap or the stack.

The stack is an area of memory where the following is stored:

  • Objects whose type is a value type
    • (e.g. enums, built-in types and structs)
  • Values of parameters passed to methods
  • References to objects created on the heap   (aka pointers)

The heap is an area of memory where the following is stored:

  • Objects that are instances of reference types
    • (e.g. strings, arrays, built-in types in the .NET framework, custom types)

Memory for objects created on the stack is allocated when a method is called or when the object is instantiated.  The memory is released when the method that instantiated the object exits.

Memory for objects created on the heap is allocated when the object is instantiated and managed by the CLR garbage collector, which frees memory periodically.

#184 – Cheating Type Safety with object Type

Since every object in C# derives from System.Object, it’s possible to “cheat” type safety by using the object type and casting objects to the desired type at run-time.

For example, assume we have a method that adds two parameters that are assumed to be numbers:

        public static double AddNums(object n1, object n2)
        {
            double d1 = Convert.ToDouble(n1);
            double d2 = Convert.ToDouble(n2);

            return d1 + d2;
        }

This is convenient because now we can pass in any numeric type we like because we can implicitly cast anything to object.

            int i1 = 5, i2 = 7;
            double d1 = 10.2, d2 = 23.2;

            // These all work as expected
            double sum = AddNums(i1, i2);
            sum = AddNums(d1, d2);
            sum = AddNums(i1, d1);

The problem is that the compiler won’t complain if we try to pass in some non-numeric object.  The following code will compile fine, but throw an exception at run-time.

            string s = "Uh-oh";
            sum = AddNums(s, 1);

#183 – Use var to Tell the Compiler to Figure out the Type

You can use traditional strong-typing in C#, where you explicitly declare the type of every variable.  Or you can use the dynamic keyword for dynamic typing, where data is only type-checked at run-time, rather than compile-time.

You can also use the var keyword when declaring variables.  When you use var, you don’t have to explicitly specify the type–the compiler will figure out the correct type at compile-time.  You’re still using strong-typing in this case, because the type checking is done at compile-time.

The following example still fails to compile.  The compiler figures out that s is of type string, it does type-checking and then complains that the use of Concat is invalid.

            var s = "Sean";
            s = s.Concat(" Sexton");

In the next example, we don’t bother to declare the exact types.

            var s = "Hemingway";
            var backwards = s.Reverse();

            // string
            Console.WriteLine(s.GetType());

            // System.Collections.Generic.IEnumerable<char>
            Console.WriteLine(backwards.GetType());

#182 – C# is (Mostly) Strongly Typed

Traditionally, C# has been considered as a strongly typed language.  But with the addition of the dynamic keyword in C# 4.0, you can choose to declare and use dynamically typed variables.  These variables are not type-checked at compile time, but only at run-time.

For example, the following code will not compile.  The String.Concat method is being used incorrectly.  (It’s a static method).

            string s = "Et tu";
            s = s.Concat(" Brutus");   // Compile-time error

In the following example, we declare the variable as dynamic, which means that the type of the variable is only determined at run-time.  No type-checking is done at compile-time.  This code will now compile fine.  The error will only be found at run-time, when an exception is thrown.

            dynamic s = "Et tu";
            s = s.Concat(" Brutus");   // This compiles

#181 – C# Is Strongly Typed

C# is a strongly typed language, meaning that every variable and object has a well-defined type.  At compile-time, the compiler checks to make sure that all operations use objects of the correct type.  This generally means that if a function takes an argument of type double, you’ll get a compile-time error if you try to pass it something that is of a different type.

There are many benefits of using dynamically typed languages (e.g. Python, Ruby).  However, the main advantage of a strongly typed language is that errors related to type conversion are caught at compile-time rather than at run-time.  It’s always better to find a bug earlier, rather than later.  Finding a bug at compile-time forces the developer to fix it.  Finding it at run-time means that the bug might only be found by a customer, after the product has shipped.

Exception: the dynamic keyword

#180 – The CLR Loads Assemblies on Demand

When you execute a .NET program, the CLR (Common Language Runtime) loads the assembly located in the executable and then starts executing the code in the function defined as the entry point for that assembly.

If your assembly references a second assembly, that second assembly will only get loaded if its code is invoked at runtime.

This means that it makes sense to partition your application into assemblies, based on functionality and expected use.  If running a program typically only results in 20% of its code being executed, the most efficient partitioning would be to have the remaining 80% of the code in one or more assemblies other than the main assembly.  If code in one of these assemblies is never called, the assembly is never loaded.

Loading as few assemblies as possible is desirable, because this results in a smaller memory footprint for your program.

#179 – What Is an Assembly?

As a .NET language, C# compiles code into assemblies. An assembly is a reusable piece of code, packaged into either an .exe or .dll file.  It contains IL (Intermediate Language) code that the CLR (Common Language Runtime) will compile into machine code at runtime.

An assembly contains a manifest, which defines high-level attributes of the assembly including its name, version, copyright information and an optional strong name that uniquely identifies it.

Assemblies also contain metadata, representing a complete description of all of the contained types.  The metadata includes a description of each method and property of each type implemented in the assembly, along with information about the parameters and return type.  An assembly also includes a list of other assemblies that it references.

An assembly is typically packaged as a single .exe or .dll file, but can also be spread across more than one file.

#178 – Throwing an Exception from a switch Statement

Instead of a break statement as a terminator of a case clause in a switch statement, you can also throw an exception.

            switch (surname)       // surname is a string
            {
                case "Aarhus":
                case "Enevoldsen":
                    Console.WriteLine("Norwegian");
                    break;
                case "Brosnan":
                case "McGill":
                    Console.WriteLine("Irish");
                    throw new ApplicationException("Software doesn't work for Irish people");
                default:
                    Console.WriteLine("UNKNOWN origin");
                    break;
            }