C#/.NET Little Wonders: The Joy of Anonymous Types
Posted
by James Michael Hare
on Geeks with Blogs
See other posts from Geeks with Blogs
or by James Michael Hare
Published on Thu, 21 Jun 2012 17:53:59 GMT
Indexed on
2012/06/22
3:17 UTC
Read the original article
Hit count: 676
Once again, in this series of posts I look at the parts of the .NET Framework that may seem trivial, but can help improve your code by making it easier to write and maintain. The index of all my past little wonders posts can be found here.
In the .NET 3 Framework, Microsoft introduced the concept of anonymous types, which provide a way to create a quick, compiler-generated types at the point of instantiation.
These may seem trivial, but are very handy for concisely creating lightweight, strongly-typed objects containing only read-only properties that can be used within a given scope.
Creating an Anonymous Type
In short, an anonymous type is a reference type that derives directly from object and is defined by its set of properties base on their names, number, types, and order given at initialization. In addition to just holding these properties, it is also given appropriate overridden implementations for Equals() and GetHashCode() that take into account all of the properties to correctly perform property comparisons and hashing. Also overridden is an implementation of ToString() which makes it easy to display the contents of an anonymous type instance in a fairly concise manner.
To construct an anonymous type instance, you use basically the same initialization syntax as with a regular type. So, for example, if we wanted to create an anonymous type to represent a particular point, we could do this:
1: var point = new { X = 13, Y = 7 };
Note the similarity between anonymous type initialization and regular initialization. The main difference is that the compiler generates the type name and the properties (as readonly) based on the names and order provided, and inferring their types from the expressions they are assigned to.
It is key to remember that all of those factors (number, names, types, order of properties) determine the anonymous type. This is important, because while these two instances share the same anonymous type:
1: // same names, types, and order
2: var point1 = new { X = 13, Y = 7 };
3: var point2 = new { X = 5, Y = 0 };
These similar ones do not:
1: var point3 = new { Y = 3, X = 5 }; // different order
2: var point4 = new { X = 3, Y = 5.0 }; // different type for Y
3: var point5 = new {MyX = 3, MyY = 5 }; // different names
4: var point6 = new { X = 1, Y = 2, Z = 3 }; // different count
Limitations on Property Initialization Expressions
The expression for a property in an anonymous type initialization cannot be null (though it can evaluate to null) or an anonymous function. For example, the following are illegal:
1: // Null can't be used directly. Null reference of what type?
2: var cantUseNull = new { Value = null };
3:
4: // Anonymous methods cannot be used.
5: var cantUseAnonymousFxn = new { Value = () => Console.WriteLine(“Can’t.”) };
Note that the restriction on null is just that you can’t use it directly as the expression, because otherwise how would it be able to determine the type? You can, however, use it indirectly assigning a null expression such as a typed variable with the value null, or by casting null to a specific type:
1: string str = null;
2: var fineIndirectly = new { Value = str };
3: var fineCast = new { Value = (string)null };
All of the examples above name the properties explicitly, but you can also implicitly name properties if they are being set from a property, field, or variable. In these cases, when a field, property, or variable is used alone, and you don’t specify a property name assigned to it, the new property will have the same name. For example:
1: int variable = 42;
2:
3: // creates two properties named varriable and Now
4: var implicitProperties = new { variable, DateTime.Now };
1: var explicitProperties = new { variable = variable, Now = DateTime.Now };
But this only works if you are using an existing field, variable, or property directly as the expression. If you use a more complex expression then the name cannot be inferred:
1: // can't infer the name variable from variable * 2, must name explicitly
2: var wontWork = new { variable * 2, DateTime.Now };
In the example above, since we typed variable * 2, it is no longer just a variable and thus we would have to assign the property a name explicitly.
ToString() on Anonymous Types
One of the more trivial overrides that an anonymous type provides you is a ToString() method that prints the value of the anonymous type instance in much the same format as it was initialized (except actual values instead of expressions as appropriate of course).
For example, if you had:
1: var point = new { X = 13, Y = 42 };
And then print it out:
1: Console.WriteLine(point.ToString());
You will get:
1: { X = 13, Y = 42 }
While this isn’t necessarily the most stunning feature of anonymous types, it can be handy for debugging or logging values in a fairly easy to read format.
Comparing Anonymous Type Instances
Because anonymous types automatically create appropriate overrides of Equals() and GetHashCode() based on the underlying properties, we can reliably compare two instances or get hash codes. For example, if we had the following 3 points:
1: var point1 = new { X = 1, Y = 2 };
2: var point2 = new { X = 1, Y = 2 };
3: var point3 = new { Y = 2, X = 1 };
If we compare point1 and point2 we’ll see that Equals() returns true because they overridden version of Equals() sees that the types are the same (same number, names, types, and order of properties) and that the values are the same. In addition, because all equal objects should have the same hash code, we’ll see that the hash codes evaluate to the same as well:
1: // true, same type, same values
2: Console.WriteLine(point1.Equals(point2));
3:
4: // true, equal anonymous type instances always have same hash code
5: Console.WriteLine(point1.GetHashCode() == point2.GetHashCode());
However, if we compare point2 and point3 we get false. Even though the names, types, and values of the properties are the same, the order is not, thus they are two different types and cannot be compared (and thus return false). And, since they are not equal objects (even though they have the same value) there is a good chance their hash codes are different as well (though not guaranteed):
1: // false, different types
2: Console.WriteLine(point2.Equals(point3));
3:
4: // quite possibly false (was false on my machine)
5: Console.WriteLine(point2.GetHashCode() == point3.GetHashCode());
Using Anonymous Types
Now that we’ve created instances of anonymous types, let’s actually use them. The property names (whether implicit or explicit) are used to access the individual properties of the anonymous type. The main thing, once again, to keep in mind is that the properties are readonly, so you cannot assign the properties a new value (note: this does not mean that instances referred to by a property are immutable – for more information check out C#/.NET Fundamentals: Returning Data Immutably in a Mutable World).
Thus, if we have the following anonymous type instance:
1: var point = new { X = 13, Y = 42 };
We can get the properties as you’d expect:
1: Console.WriteLine(“The point is: ({0},{1})”, point.X, point.Y);
But we cannot alter the property values:
1: // compiler error, properties are readonly
2: point.X = 99;
Further, since the anonymous type name is only known by the compiler, there is no easy way to pass anonymous type instances outside of a given scope. The only real choices are to pass them as object or dynamic. But really that is not the intention of using anonymous types. If you find yourself needing to pass an anonymous type outside of a given scope, you should really consider making a POCO (Plain Old CLR Type – i.e. a class that contains just properties to hold data with little/no business logic) instead.
Given that, why use them at all? Couldn’t you always just create a POCO to represent every anonymous type you needed? Sure you could, but then you might litter your solution with many small POCO classes that have very localized uses.
It turns out this is the key to when to use anonymous types to your advantage: when you just need a lightweight type in a local context to store intermediate results, consider an anonymous type – but when that result is more long-lived and used outside of the current scope, consider a POCO instead.
So what do we mean by intermediate results in a local context? Well, a classic example would be filtering down results from a LINQ expression. For example, let’s say we had a List<Transaction>, where Transaction is defined something like:
1: public class Transaction
2: {
3: public string UserId { get; set; }
4: public DateTime At { get; set; }
5: public decimal Amount { get; set; }
6: // …
7: }
And let’s say we had this data in our List<Transaction>:
1: var transactions = new List<Transaction>
2: {
3: new Transaction { UserId = "Jim", At = DateTime.Now, Amount = 2200.00m },
4: new Transaction { UserId = "Jim", At = DateTime.Now, Amount = -1100.00m },
5: new Transaction { UserId = "Jim", At = DateTime.Now.AddDays(-1), Amount = 900.00m },
6: new Transaction { UserId = "John", At = DateTime.Now.AddDays(-2), Amount = 300.00m },
7: new Transaction { UserId = "John", At = DateTime.Now, Amount = -10.00m },
8: new Transaction { UserId = "Jane", At = DateTime.Now, Amount = 200.00m },
9: new Transaction { UserId = "Jane", At = DateTime.Now, Amount = -50.00m },
10: new Transaction { UserId = "Jaime", At = DateTime.Now.AddDays(-3), Amount = -100.00m },
11: new Transaction { UserId = "Jaime", At = DateTime.Now.AddDays(-3), Amount = 300.00m },
12: };
So let’s say we wanted to get the transactions for each day for each user. That is, for each day we’d want to see the transactions each user performed. We could do this very simply with a nice LINQ expression, without the need of creating any POCOs:
1: // group the transactions based on an anonymous type with properties UserId and Date:
2: byUserAndDay = transactions
3: .GroupBy(tx => new { tx.UserId, tx.At.Date })
4: .OrderBy(grp => grp.Key.Date)
5: .ThenBy(grp => grp.Key.UserId);
Now, those of you who have attempted to use custom classes as a grouping type before (such as GroupBy(), Distinct(), etc.) may have discovered the hard way that LINQ gets a lot of its speed by utilizing not on Equals(), but also GetHashCode() on the type you are grouping by. Thus, when you use custom types for these purposes, you generally end up having to write custom Equals() and GetHashCode() implementations or you won’t get the results you were expecting (the default implementations of Equals() and GetHashCode() are reference equality and reference identity based respectively).
As we said before, it turns out that anonymous types already do these critical overrides for you. This makes them even more convenient to use! Instead of creating a small POCO to handle this grouping, and then having to implement a custom Equals() and GetHashCode() every time, we can just take advantage of the fact that anonymous types automatically override these methods with appropriate implementations that take into account the values of all of the properties.
Now, we can look at our results:
1: foreach (var group in byUserAndDay)
2: {
3: // the group’s Key is an instance of our anonymous type
4: Console.WriteLine("{0} on {1:MM/dd/yyyy} did:", group.Key.UserId, group.Key.Date);
5:
6: // each grouping contains a sequence of the items.
7: foreach (var tx in group)
8: {
9: Console.WriteLine("\t{0}", tx.Amount);
10: }
11: }
And see:
1: Jaime on 06/18/2012 did:
2: -100.00
3: 300.00
4:
5: John on 06/19/2012 did:
6: 300.00
7:
8: Jim on 06/20/2012 did:
9: 900.00
10:
11: Jane on 06/21/2012 did:
12: 200.00
13: -50.00
14:
15: Jim on 06/21/2012 did:
16: 2200.00
17: -1100.00
18:
19: John on 06/21/2012 did:
20: -10.00
Again, sure we could have just built a POCO to do this, given it an appropriate Equals() and GetHashCode() method, but that would have bloated our code with so many extra lines and been more difficult to maintain if the properties change.
Summary
Anonymous types are one of those Little Wonders of the .NET language that are perfect at exactly that time when you need a temporary type to hold a set of properties together for an intermediate result. While they are not very useful beyond the scope in which they are defined, they are excellent in LINQ expressions as a way to create and us intermediary values for further expressions and analysis.
Anonymous types are defined by the compiler based on the number, type, names, and order of properties created, and they automatically implement appropriate Equals() and GetHashCode() overrides (as well as ToString()) which makes them ideal for LINQ expressions where you need to create a set of properties to group, evaluate, etc.
© Geeks with Blogs or respective owner