DaedTech

Stories about Software

By

Type Variance: Generics, Reflection, Anonymous and Dynamic

In working with C# quite heavily over the last few years and talking to a lot of people in different capacities, I’ve noticed a fair bit of confusion on the subject of object types and their attendant rules. So I’d thought I’d offer a post to help understand different factors that go into schemes for declaring types, using them, and keeping them in sync. Hopefully this helps you if you’ve found yourself confused in the past:

Generics

Just about anyone who has ever worked with C# is familiar with generics at least as a client. If you’ve ever declared a List, for instance, you’re using a generic. If you’ve been at it for a while and have a few more notches on your C# belt, you’ve probably written a class with a format like the following:

public class ConsoleWriter
{
public void WriteToConsole(T target)
{
Console.WriteLine(target);
}
}

Useless as it is, this class is instantiated with some type and operates on arguments of that type. For instance, if you declare a writer with integer, it will accept integer inputs and write them to the console:

var writer = new ConsoleWriter();
writer.WriteToConsole(24);

If you try to write to the console with some type other than int here, you’ll wind up with a compiler error. The reason for the error is that generics are a compile-time way of specifying type variance. That is, the compiler knows exactly what T will be as the code is being compiled and thus it is able to tell you if you’re supplying the wrong kind of T. And this makes sense when you think about it — you’re instantiating a List or ConsoleWriter, so the compiler knows that you intend for your instances to deal with ints.

Another way to think about generics is that a generic class is a sort of class “stub” that is waiting for additional information to be supplied when you create instances. That is, a ConsoleWriter is meaningless in a non-instantiated context in the same way as an abstract base class. It needs more information — it needs you to come along and say via instantiation, “this is the kind of ConsoleWriter I’m going to use.”

When you see a “T” (or any other generic descriptor) remember that it will not exist at runtime. In other words, when the program is actually executing, there is no ConsoleWriter<T> but only ConsoleWriter<int> . From a runtime perspective, you may as well have an actual class called “IntConsoleWriter” as far as the runtime is concerned. Generic parameters are meaningless when the application runs.

GetType()

Another way of exploring variability in types is with the object.GetType() method. This method takes advantage of two important facets of C#: (1) the fact that everything inherits from object and (2) reflection. Reflection is concept specific to managed languages (such as Java and C#) that allows executing code to inspect the state of its own source code (in a manner of speaking, but this is an oversimplification). For instance, if you had the class below, it would print “A” and then “B” to the console on separate lines when you called its DescribeYourselfToTheConsole() method.

class SelfDescriber
{
public int A { get; set; }

public int B { get; set; }

public void DescribeYourselfToTheConsole()
{
foreach (var property in this.GetType().GetProperties())
Console.WriteLine(property.Name);
}
}

For those who may not be familiar with reflection or fully aware of its usages, this is quite a powerful concept. As someone who cut his teeth on C and C++, I recall my first exposure to this many moons ago and thinking “you can do what?!?” Awesome!!!” Beware though — reflection is slow and resource intensive.

When you call GetType() on some object, you’re actually accessing the state of that object in code. Unlike generics, this is purely a run-time concept. And that makes sense as well — why would you need to lookup information about a class at compile time? You could just inspect the source code at that point. But at runtime, you might not know exactly what type you’re dealing with. Take a look at this modified version of ConsoleWriter from the last example:

public class ConsoleWriter
{
public void WriteToConsole(object target)
{
Console.WriteLine(string.Format("Value {0} is of type {1}", target, target.GetType()));
}
}

Console writer is getting an object passed to it, but that’s not particularly helpful in understanding more details about it. In unmanaged languages like C++, all you could really do in this situation is cast the object and hope for the best. But in managed languages like C#, you can actually inspect it to see what type it is and then do things with it accordingly. In this example, we simply print out its type, but you also have the option of doing things like saying if(target.GetType() == typeof(int)) or if(target is int) and then doing int-specific things.

Again, all of this happens at run-time when different execution paths dictate non-deterministic instance typing. And that’s just a fancy of way of saying that in an object-oriented, polymorphic language, you can’t know at compile time what instances you’re going to have at runtime since your instance creation may depend on external factors (for instance, if you have factory methods). So reflection is a runtime way to understand what type you’re dealing with.

Anonymous Classes

I’ve seen a lot of confusion around the subject of Anonymous classes. Sometimes I see them confused with dynamic types (covered next) and sometimes I see them misunderstood. Don’t think overthink when it comes to anonymous types — they’re just classes that don’t have names. Why don’t they have names? Because they’re declared inline in code instead of in the standard class structure. For instance:

public void SomeMethod()
{
var noNameType = new { customerId = 1, customerName = "Erik" };
Console.WriteLine(string.Format("Name is {0}", noNameType.customerName));
}

Here I’ve declared a type that has no name, but a Customer by any other name… well, you get the drift. In C# we can declare types inline this way. As it turns out, this comes in quite handy with Linq, IQueryable and its extension methods. It also gives rise to the “var” keyword that you see me use there, which is one of the most misunderstood language constructs there is. If I had a dollar for every time I’ve seen someone mistake this for the VB construct “dim” or some other dynamic typing moniker, I wouldn’t be Warren Buffet, but I’d definitely have enough money for a night on the town. “var” is the syntax for handling implied typing, which is the only means you have for declaring anonymous types (I mean, what else could you do seeing as they don’t have names?)

Anonymous typing is a compile time way of specifying types, like generics. However, unlike generics, anonymous types are not templates. They are normal, full blown, honest to goodness types that simply happen not to have names. If you tried to assign one to int or an int to one, the code would not compile, just as with any other static type that you’d made a class for.

Dynamic

In that last sentence, I mentioned static typing, which describes languages where reference values are declared and checked at compile time. In other words, with static typing, you would say “int x = 6” whereas in a dynamically typed language such as JavaScript you might simply say “x = 6” and thus it is up to the runtime interpreter to figure out that you’ve declared an integer. C# has its roots heavily in the camp of statically typed language, but in C# 4.0, the concept of dynamic typing (sometimes referred to as “duck typing”) was introduced.

The “dynamic” keyword basically tells the C# compiler, “this guy’s with us and he’s cool — no need to check him for weapons.” It’s then waved on through. For instance, the following code compiles without issue:

dynamic noNameType = "asdf";
noNameType = 12;

Assert.AreEqual(12, noNameType.ToString());

However, the test fails with a “RuntimeBinderException” meaning that he did have weapons after all. noNameType was an integer and so its ToString() method evaulated to a string, which normally wouldn’t have compiled but here failed at runtime. You can do all sorts of wacky stuff — instead of “ToString()” you could have said .AsadfFdsafasdfasf() and it still would have compiled (but obviously failed at runtime since that’s not a method on int).

Why would you want to do this, particularly in an otherwise statically typed language? Well, there are some use cases with painful interop and COM scenarios where getting ahold of actual types is surprisingly difficult and simply invoking methods you need to be there is comparably easy. Additionally, there are some interesting concepts like the expando object and doing things like impelenting interfaces at runtime, which can be useful if you’re doing rather intricate things like writing a mocking engine.

I would advise a good bit of caution here, though. C# is not an interpreted language and everyone reading your code and working with it is going to be used to and expecting statically typed code. You should probably only use dynamic typing when it’s onerous not to, unlike the other techniques I’ve covered here.

Wrap Up

So there you have it. We have two typing techniques that are compile time techniques (generics and anonymous types) and two that are run-time techniques (GetType()/reflection and dynamic typing). Generics are classes (or methods) that are templates allowing a type to use to be specified by clients at compile time. GetType() and reflection can be used to explore properties of types at runtime. Anonyomus types allow specification of an actual, first class type without the ceremony of an entire defined class, and dynamics allow us to ‘trick’ the compiler and break the rules… when we know (think) it’ll be okay. Take care with that last one, and use in good health. Hopefully this clears up the odd misconception or confusion you may have had about one or more of these concepts.

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Timothy Boyce
Timothy Boyce
11 years ago

In the paragraph before GetType(), I think you lost some brackets in one of the ConsoleWriters.

Erik Dietrich
11 years ago
Reply to  Timothy Boyce

You’re exactly right — duly corrected. Thanks!

trackback

[…] but it makes sense when you think about it. The reason it makes sense, if you’ll recall my post on type variance, is that the type of the enumerable is generic and strictly a compile time designation. As such, […]