A2Z.Net : A – Anonymous Types

This article marks the beginning of a new series of posts, which aims to dig deeper into 26 different features of .Net. For the first article, or the A – I have selected Anonymous Types. I would have chosen asynchronous programming, but I have whole dedicated series on asynchronous programming which could followed in this link.

Introduced in .Net 3.x, Anonymous Types has made the life easy for the developers by handling out the ability to work on an encapsulated type, without having to actually define one. I should say encapsulate is a wrong word here (but have used it so that I could highlight why) because you cannot place any behavior with the properties.

Anonymous Types and Syntax

Anonymous Type provides an easy way to encapsulate a set of properties without having to explicitly define a type. Notice that I used the word explicitly here – that is because the compiler creates the type for you under the hood. We will look into what happens behind the scenes a little later, to begin with, let us introduce the syntax of defining an anonymous type.

var user = new
{
    UserName ="Anu Viswan",
    Age = userRecord.Age,
    JoiningDate= DateTime.Now
};

The above code uses object initializer (without having to specify the data type) to create an instance of an anonymous type initialized with 3 properties – namely UserName(string),Age(int), and JoiningDate (DateTime). You could also create an anonymous type.

As I mentioned earlier, the compiler generates a type under the hood and ensure the code is still statically typed.

Since the type of anonymous Type is known statically, they must be stored using variables with var allowing type inference. You could also store them as object, but that would defeat the whole purpose of anonymous types.

In addition to the creation syntax mentioned above, framework also provides another way initializing an anonymous type. This syntax is known as projection initializer, and it allows you to use the property/field names from somewhere else.

var person = new Person
{
    FirstName = "Anu",
    LastName = "Viswan",
    Age = 36
};

var user = new
{
    person.FirstName,
    person.Age,
};

The above code uses a part of type Person to create an anonymous type. In this case, as you can notice, you are not explictly providing names for properties, instead, they uses the same name as the Person. This is of course equavalent of writing as

var user = new
{
    FirstName = person.FirstName,
    Age = person.Age
};

The projection initializer syntax is highly useful in Linq queries when you need to project a subset of a query or combine properties from multiple objects. For Example,

var personList = new List<Person>
{
	new Person {UserName="Anu Viswan",    Age = 36},
	// Other Persons
};

var employeeList = new List<Employee>
{
    new Employee{UserName="Anu Viswan",JoiningDate=new DateTime(2020,2,2)},
    // Other employee records //
};

var user = employeeList.Join(personList,
							e=>e.UserName,
							p=>p.UserName,
							(e,p)=> new
								{
									e.UserName,
									UserDetail = new  // An example of Nested Anonymous Type
									{
									e.JoiningDate,
									p.Age,
									QueryTimeStamp=DateTime.Now
									}
								});

As you can observe the creation of anonymous types are pretty simple. You could also create a type combining multiple objects as seen in above example. The above code also demonstrates that the anonymous types could be nested. UserDetail is an anonymous type within the user.

Behind the Scenes

Alright, enough of syntax, let us now look behind the scenes now. Let us bring back our first example of anonymous type and see how the type generated by the compiler looks like.

var user = new
{
    UserName = "Anu Viswan",
    Age = 36,
    JoiningDate= DateTime.Now
};

The compiler generated (_generated using ILSpy) type would look like

[CompilerGenerated]
[DebuggerDisplay("\\{ UserName = {UserName}, Age = {Age}, JoiningDate = {JoiningDate} }", Type = "<Anonymous Type>")]
internal sealed class <>f__AnonymousType0<<UserName>j__TPar, <Age>j__TPar, <JoiningDate>j__TPar>
{
	[DebuggerBrowsable(DebuggerBrowsableState.Never)]
	private readonly <UserName>j__TPar <UserName>i__Field;

	[DebuggerBrowsable(DebuggerBrowsableState.Never)]
	private readonly <Age>j__TPar <Age>i__Field;

	[DebuggerBrowsable(DebuggerBrowsableState.Never)]
	private readonly <JoiningDate>j__TPar <JoiningDate>i__Field;

	public <UserName>j__TPar UserName => <UserName>i__Field;

	public <Age>j__TPar Age => <Age>i__Field;

	public <JoiningDate>j__TPar JoiningDate => <JoiningDate>i__Field;

	[DebuggerHidden]
	public <>f__AnonymousType0(<UserName>j__TPar UserName, <Age>j__TPar Age, <JoiningDate>j__TPar JoiningDate)
	{
		<UserName>i__Field = UserName;
		<Age>i__Field = Age;
		<JoiningDate>i__Field = JoiningDate;
	}

	[DebuggerHidden]
	public override bool Equals(object value)
	{
		// Hidden : Will be discussed later
	}

	[DebuggerHidden]
	public override int GetHashCode()
	{
		// Hidden : Will be discussed later
	}

	[DebuggerHidden]
	public override string ToString()
	{
		// Hidden : Will be discussed later
	}
}


Characteristics of Generated Code

Let us break down the generated code and examine it in parts.

It is a Generic Class

As you would have already observed, the generated type is a class, name of which is generated by the compiler and cannot be accessed from your source code.

internal sealed class <>f__AnonymousType0<<UserName>j__TPar, <Age>j__TPar, <JoiningDate>j__TPar>
{
    public <>f__AnonymousType0(<UserName>j__TPar UserName, <Age>j__TPar Age, <JoiningDate>j__TPar JoiningDate)
	{
		<UserName>i__Field = UserName;
		<Age>i__Field = Age;
		<JoiningDate>i__Field = JoiningDate;
	}
}

It is also interesting to note that the generated type is a Generic class, with one type parameter for each of the properties. This would mean that if there is a second string, it would still have a different type parameter than the original one. For example,

var user = new
{
    UserName = "Anu Viswan",
    FirstName = "Anu"
};

The above would translate to

internal sealed class <>f__AnonymousType0<<UserName>j__TPar, <FirstName>j__TPar>
{
}

Note that the class is marked internal and sealed. Now both these characterstics aren’t guaranteered as per Jon Skeet’s book C# in Depth. This is actually interesting, as to know when and why the compiler could not guarante a consistent behavior.

The generated type also has a constructor which accepts parameters which are in consistent with all the properties in the type.

Read Only Properties

The other characterstics of the generated type that is quite obvious is that all properties are read-only. One cannot reassign properties of an anonymous type. If the need arise, you would have to recreate the entire object.

var user = new
{
    UserName = "Anu Viswan",
    Age = 36,
    JoiningDate = DateTime.Now,
};

user.Age = 37; // This line would throw error

user = new  // The correct approach would be recreate
{
    UserName = user.UserName,
    Age = 37,
    JoiningDate = user.JoiningDate,
};

Derieved from System.Object & Overrides Equals()/GetHashCode()

The generated class for anonymous types is derieved from System.Object. It cannot be, hence, cast to any other type other than Object. The generated type also overrides the Equals() and GetHashcode() methods.

public override bool Equals(object value)
{
    <>f__AnonymousType0<<UserName>j__TPar, <Age>j__TPar, <JoiningDate>j__TPar> anon = value as <>f__AnonymousType0<<UserName>j__TPar, <Age>j__TPar, <JoiningDate>j__TPar>;

    return anon != null && EqualityComparer<<UserName>j__TPar>.Default.Equals(<UserName>i__Field, anon.<UserName>i__Field) && EqualityComparer<<Age>j__TPar>.Default.Equals(<Age>i__Field, anon.<Age>i__Field) && EqualityComparer<<JoiningDate>j__TPar>.Default.Equals(<JoiningDate>i__Field, anon.<JoiningDate>i__Field);
}

public override int GetHashCode()
{
    return ((459989953 * -1521134295 + EqualityComparer<<UserName>j__TPar>.Default.GetHashCode(<UserName>i__Field)) * -1521134295 + EqualityComparer<<Age>j__TPar>.Default.GetHashCode(<Age>i__Field)) * -1521134295 + EqualityComparer<<JoiningDate>j__TPar>.Default.GetHashCode(<JoiningDate>i__Field);
}

These ensures that two instances of AnonymousTypes are equal only if all the properties are equal. Do note that the exact implementation of GetHashCode() might also vary. For example,

var user = new
{
    UserName = "Anu Viswan",
    Age = 36,
    JoiningDate = new DateTime(2020,10,10),
};

var anotherInstance = new
{
    UserName = "Anu Viswan",
    Age = 36,
    JoiningDate = new DateTime(2020, 10, 10),
};

Console.WriteLine($"user & anotherInstance - Equality :{user.Equals(anotherInstance)}"); // True

Reuse of Anonymous Types

If two anonymoys types are created with same Property Names, in same order and type, then the compiler would treat the two objects as of same type, ie, essentially reuse the generated type.

For example, the preceeding code would reuse the same generated type.

Console.WriteLine($"user & anotherInstance - Type Equality :{user.GetType() == anotherInstance.GetType()}"); // True

This could verified when we check the generated code.

<>f__AnonymousType0<string, int, DateTime> user = new <>f__AnonymousType0<string, int, DateTime>("Anu Viswan", 36, new DateTime(2020, 10, 10));

<>f__AnonymousType0<string, int, DateTime> anotherInstance = new <>f__AnonymousType0<string, int, DateTime>("Anu Viswan", 36, new DateTime(2020, 10, 10));

However, if you change the order of Properties in either of the anonymous types, the compiler would generate another type. Consider the following code.

 var user = new
 {
     UserName = "Anu Viswan",
     Age = 36,
     JoiningDate = new DateTime(2020,10,10),
 };
  var differentInstance = new
 {
     UserName = "Anu Viswan",
     JoiningDate = new DateTime(2020, 10, 10),
     Age = 36,
 };

As you can observe, while the PropertyNames and Types are preserved in the two anonymous types, the order is changed.The generated code would be as following.

<>f__AnonymousType0<string, int, DateTime> user = new <>f__AnonymousType0<string, int, DateTime>("Anu Viswan", 36, new DateTime(2020, 10, 10));

f__AnonymousType1<string, DateTime, int> differentInstance = new <>f__AnonymousType1<string, DateTime, int>("Anu Viswan", new DateTime(2020, 10, 10), 36);

As seen in the generated code, the compiler generates and use different internal types for the two anonymous types.

This holds true only when the two anonymous types are in the same assembly. If the two anonymous types are defined in two different assemblies, compiler would generate two different types for each. We will explore this later in this article.

Points Of Interest

The above section outlined the characterstics of Anonymous Types. There are however, certain points which is worth noting while learning about Anonymous Types.

Pass as Parameter/ Return Type

Since the generated type is not accessible/addressed by the developer in his code, it to an extend limits the transfer an anonymous types from one method to another as you cannot express the type as Parameter. However, you could make use of Generics to pass the anonymous types around. For example,

 public void Print<T>(T data)
 {
     var properties = typeof(T).GetProperties();
     foreach(var property in properties)
     {
         Console.WriteLine($"{property.Name} = { property.GetValue(data)}");
     }
 }

The compiler here treats the generated type as any reference type in the system. This allows us to use generics to pass anonymous types around.

You could also pass the anonymous type as an object or dynamic parameter as well, however, that would take away any benefits associated to anonymous types.

Similiar Anonymous Types in Two Assemblies

In the earlier section we noticed how the compiler would reuse the same generated type if the Property Names, Order and Types are the same. However, there is a caveat. The generated types are localized to the assembly.

If the two types resides in different assembly, then the compiler would generate different Anonymous Types for each of them.

Consider the following scenario.

// Assembly 1
 var user = new
 {
     UserName = "Anu Viswan",
     Age = 36,
     JoiningDate = new DateTime(2020,10,10),
 };
 var bar = new Bar();
 bar.Print(user);


// Assembly 2
public class Bar
{
    public void Print(object data)
    {
        var typeSchema = new { UserName = string.Empty, Age = default(int), JoiningDate = default(DateTime) };
        var castInstance = Cast(typeSchema, data);
        Console.WriteLine(data.GetType() == castInstance.GetType());
    }
    private static T Cast<T>(T typeHolder, Object x) where T : class
    {
        return (T)x;
    }
}

The above casting would raise an InvalidCastException as the two types resides in different assemblies, resulting in compiler generating two different anonymous types.

This would have been a succesful cast if the code resided in the same assembly.

Conclusion

As observed, the implement of Anonymous Types is pretty simple, and so is its usage. The compiler generates a generic class for each unique anonymous type within the assembly. The feature has been extensively used since Linq came along as it allows to create subsets from queries without having to define an explicit type.

The complete code citd in this article is available in my Github.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s