How to Recover Anonymous Types
When we just need a quick and dirty type to hold some values, C#’s anonymous types fit the bill: var person = { First="John", Last="Doe", Age=42 }
. On the down side, since these types are anonymous they have no explicit type. The var
variable is strongly typed, but you have to use the object
type when passing them to other functions. But then how do you get the fields back out? Today’s article shows you how so that anonymous types will be more useful to you. Read on to find out how to recover anonymous types!
Today’s article is inspired by an exchange with Taraa in the comments section of another article.
Let’s start simply by using anonymous types:
void Start() { var fullName = new { First = "Jackson", Last = "Dunstan" }; var person = new { First = "Jackson", Last = "Dunstan", Location = "California" }; Debug.Log(fullName.GetType()); Debug.Log(person.GetType()); }
This prints out the names of the classes that the C# compiler automatically generated for us to support our type anonymous types:
<>__AnonType0`2[System.String,System.String] <>__AnonType1`3[System.String,System.String,System.String]
The first type has two string
properties and the second has three, just as we’d expect. But this raises an interesting question: would the compiler re-use the generated type if another function created an anonymous type instance with the same fields? Let’s try that out and see:
void Start() { // .. same as above CheckSameTypes(fullName.GetType(), person.GetType()); } void CheckSameTypes(Type fullNameType, Type personType) { // Same field names // Same field types // Different values // Different function var fullName = new { First = "John", Last = "Doe" }; var person = new { First = "John", Last = "Doe", Location = "Anytown" }; // Are they the same? Debug.Log(fullName.GetType() == fullNameType); Debug.Log(person.GetType() == personType); }
Indeed, both of these lines print true
. This means that the compiler reused the same generated types even across functions. This is even true if you use the type in another class in your Unity project, but I’ll omit that for brevity’s sake.
This discovery opens the door for us to recover the strong typing, but first let’s look at the problem we’re trying to solve. Consider a simple function that wants to print the first and last name:
void PrintFullName(object fullName) { // compiler errors: Debug.Log(fullName.First + " " + fullName.Last); }
Here you get a compiler error:
error CS1061: Type `object' does not contain a definition for `First' and no extension method `First' of type `object' could be found (are you missing a using directive or an assembly reference?)
The reason for this is that we were forced to take our strongly-typed var
local variable in the Start
function and pass it to PrintFullName
as an object
-typed parameter. Since object
is the root of all types in C#, it’s certainly not going to have our First
and Last
fields on it!
So how do we recover the anonymous type so we can print the First
and Last
fields? We clearly need to cast the object
parameter to the anonymous type with those fields, but there’s no name for that type so we need to find another way to express it to the compiler. Luckily, we’ve found that instantiating another with the same field names and types results in the same generated class type, so let’s start there:
void PrintFullName(object fullName) { var tempFullName = new { First = "", Last = "" }; // compiler errors: Debug.Log(fullName.First + " " + fullName.Last); }
Now how do we cast from object
to the type that tempFullName
has? A helper function with a type parameter can do that:
T Cast<T>(object obj, T type) { return (T)obj; }
This function takes an object
, which we have, and casts it to a T
, which can be whatever we want it to be. Let’s use the helper function in PrintFullName
to recover the typed object:
void PrintFullName(object fullName) { var typed = Cast(fullName, new { First = "", Last = "" }); // compiler errors: Debug.Log(fullName.First + " " + fullName.Last); }
Now we have typed
as a var
local variable with the same type as the caller’s anonymous type! The final step is to simply use it instead of the object
-typed parameter:
void PrintFullName(object fullName) { var typed = Cast(fullName, new { First = "", Last = "" }); Debug.Log(typed .First + " " + typed .Last); }
There are two big caveats to all of this. First, as far as I know there’s no guarantee that the compiler will use the same generated class for any two anonymous type instances. It just happens to work this way with Unity’s Mono compiler. If it ever changes and you rely on this behavior, your code will break.
Second, you are still passing an untyped object
parameter and casting, so there’s no compile-time checking that the parameter you pass is correct. For example, if you try to pass person
instead of fullName
, you’ll get a runtime error because the compiler generated a different class for it’s three strings:
InvalidCastException: Cannot cast from source type to destination type.
So consider this a neat trick that can be helpful in a pinch, but you should usually use hand-written classes and interfaces when you need to pass objects to other functions.
Today’s article sprung from a discussion in the comments section on another article. Feel free to post your questions and comments on this article, especially if you’ve had any experiences—good or bad—with C#’s anonymous types.