Anonymous Types

Intertech Tutorials

Understanding Anonymous Types

As an object-oriented programmer, you know the benefits of defining classes and structures to represent the state and functionality of a given programming entity. Whenever you need to define a user-defined type that is intended to be reused across projects, creating a new class or structure is common practice. Many of these classes provide numerous bits of functionality through a set of methods, events, properties, and custom constructors.

Other times, you may wish to define a class simply to model a set of encapsulated and somehow related data points without any associated functionality. Imagine you wish to define the ‘shape’ of data (three strings and one integer) but have no need to define custom members (methods, events, and so on). Furthermore, what if this type is only used internally by a small part of your application and is not intended to be reused? If you need such a temporary type, you would need to build a new class definition by hand:


class SomeClass
{
// Define a set of private member variables.

// Make a property for each member variable.

// Override ToString() to account for each member variable.

// Override Equals() to account for value-based semantics.

// Override GetHashCode().
}

.NET 3.5 now allows you to define an ‘anonymous type’. This can be very helpful when you simply wish to define the ‘shape’ of a type without any custom functionality. Essentially, this allows you to build UDTs (user-defined types) on the fly without literally defining the class or structure in code. In most programming assignments, this feature will be most useful when interacting with LINQ technologies.

When you define an anonymous type, you do so by making use of implicitly typed local variables in conjunction with object initialization syntax. Each name/value pair can be accessed after the fact using CLR property syntax. Consider the following anonymous type that models a simple automobile type:


// C#
static void CreateAndUseAnonymousType()
{
// Make an anonymous type representing a car.
var myCar =
new { Color = "Black", Make = "Saab", CurrentSpeed = 55 };

Console.WriteLine("My car is the color {0}.", myCar.Color);
}

linq22

' VB
Sub CreateAndUseAnonymousType()

' Make an anonymous type representing a car.
Dim myCar = New With {.Color = "Black", .Make = "Saab", _
.CurrentSpeed = 55}

Console.WriteLine("My car is the color {0}.", myCar.Color)
End Sub

C# and VB differ radically on the underlying treatment of the name/value pairs defined by an anonymous type. In C#, each name/value pair results in read-only properties. In VB, each name/value pair results in read-write properties.

Consider the following difference in overall functionally. Notice how it is a compiler error to change the state of an anonymous type instance in C#.


// C#
static void CreateAndUseAnonymousType()
{
C# underlying properties are read only!
var myCar =
new { Color = "Black", Make = "Saab", CurrentSpeed = 55 };
myCar.Color = "Pink"; // ERROR in C#!
Console.WriteLine("My car is the color {0}.", myCar.Color);
}



' VB
Sub CreateAndUseAnonymousType()

' VB underlying properties are read/write!
Dim myCar = New With {.Color = "Black", .Make = "Saab", _
.CurrentSpeed = 55}
myCar.Color = "Pink" ' OK in VB!

' Color would now be "Pink"
Console.WriteLine("My car is the color {0}.", myCar.Color)
End Sub

Internal Composition of Anonymous Types

All anonymous types are automatically derived from System.Object and, therefore, support each of the members provided by this base class. Given this, you could invoke ToString(), GetHashCode(), Equals(), or GetType() on the anonymous myCar variable. Consider the following helper method, which invokes various members of Object for diagnostic purposes.


// C# (VB code would be similar)
static void ReflectOverAnonymousType(object obj)
{
Console.WriteLine("obj is an instance of: {0}",
obj.GetType().FullName);
Console.WriteLine("Base class of {0} is {1}",
obj.GetType().Name, obj.GetType().BaseType);
Console.WriteLine("obj.ToString() = {0}", obj.ToString());
Console.WriteLine("obj.GetHashCode() = {0}", obj.GetHashCode());
Console.WriteLine();
}

Now, consider the following usage and resulting output.


// C# (VB code would be similar)
static void Main(string[] args)
{
// Make an anonymous object representing a car.
var myCar = new {Color = "Black", Make = "Saab", CurrentSpeed = 55};

// Reflect over what the compiler generated.
ReflectOverAnonymousType(myCar);
}

linq7

Here, the anonymous type has been assigned a unique name, which Is-A System.Object. Understand that the assigned type name is completely determined by the compiler and is not accessible in your code base. Your type names will differ.

Beyond the autogenerated name, each compiler-generated type has the following characteristics:

The compiler-generated implementation of ToString() simply makes use of StringBuilder to return the current set of name/value pairs. Again, the names of the properties are a direct result of the anonymous type definition.


// Approximate C# code.
public override string ToString()
{
StringBuilder builder1 = new StringBuilder();
builder1.Append("{");
builder1.Append("Color");
builder1.Append("=");
builder1.Append(this.Color);
builder1.Append(", ");
builder1.Append("Make");
builder1.Append("=");
builder1.Append(this.Make);
builder1.Append(", ");
builder1.Append("CurrentSpeed");
builder1.Append("=");
builder1.Append(this.CurrentSpeed);
builder1.Append("}");
return builder1.ToString();
}

The compiler-generated implementation of GetHashCode() returns a hash value based on the hash values of each named property:


// Approximate C# code.
public override int GetHashCode()
{
int hashValue = 0;
if (this.Color != null)
{
hashValue ^= this.Color.GetHashCode();
}
if (this.Make != null)
{
hashValue ^= this.Make.GetHashCode();
}
return (hashValue ^ this.CurrentSpeed.GetHashCode());
}

The compiler-generated implementation of Equals() is a bit more interesting. The Equals() method is implemented to make use of value-based semantics. Therefore, if you have two anonymous types that have identical name/value pairs, Equals() returns true. However, the compiler does not overload the equality operators for an anonymous type.

Recall that by default, the C# equality operators are making use of reference-based semantics. Thus, if you use these C# operators to test for equality of two anonymous types, the return value is false.

However, VB anonymous types can only be tested for equality by calling Equals(). Unlike C#, it is a compiler error to use the VB equality operators to test anonymous types. If you need to test for reference- based equality in VB, call the shared Object.ReferenceEquals() method.

Consider the following code and resulting output. Notice that firstCar and secondCar have identical name/value pairs.


// C#
// Make two anonymous classes with identical name/value pairs.
var firstCar = new { Color = "Bright Pink",
Make = "Saab", CurrentSpeed = 55 };
var secondCar = new { Color = "Bright Pink",
Make = "Saab", CurrentSpeed = 55 };

// Are they considered equal when using Equals()? Yes!
if (firstCar.Equals(secondCar))
Console.WriteLine("Same anonymous object!");
else
Console.WriteLine("Not the same anonymous object!");

// Are they considered equal when using ==? No!
// VB programmers would need to call
// Object.ReferenceEquals(firstCar, secondCar)
if (firstCar == secondCar)
Console.WriteLine("Same anonymous object!");
else
Console.WriteLine("Not the same anonymous object!");

// Are these objects the same underlying type? Yes!
if (firstCar.GetType().FullName == secondCar.GetType().FullName)
Console.WriteLine("We are both the same type!");
else
Console.WriteLine("We are different types!");

linq23

Anonymous types can be composed using additional anonymous types. For example, consider the following entity, which models a purchase order for a given automobile. The screen shot is the result of calling ToString() on the purchaseItem object.


// C#
// Make an anonymous type that is composed of another.
var purchaseItem = new {
TimeBought = DateTime.Now,
ItemBought = new {Color = "Red", Make = "Saab", CurrentSpeed = 55},
Price = 34.000};

linq32-529x49

' VB
' Make an anonymous type that is composed of another.
Dim purchaseItem = New With { _
.TimeBought = DateTime.Now, _
.ItemBought = New With {.Color = "Red", .Make = "Saab",
.CurrentSpeed = 55}, _
.Price = 34.0}

When working with anonymous types, remember the following limitations:

Given these limitations, remember this feature is only useful when you wish to quickly model the ‘shape’ of a type, not its functionality. Like many of the topics shown in this chapter, anonymous types are quite useful when working with LINQ technologies. For example, using anonymous types, you can build LINQ queries that project new forms of data from a container. Whenever you need to model behaviors and functionality, don’t bother with an anonymous type. Just make a traditional class/structure.


Copyright (c) 2008-2013. Intertech, Inc. All Rights Reserved. This information is to be used exclusively as an online learning aid. Any attempts to copy, reproduce, or use for training is strictly prohibited.