667 words
3 minutes
Understanding C# Features (3) Implicit Type and Immutable Anonymous Type

[LINQ via C#] - [C# Features]

Local variable type inference#

The var keyword is introduced since C# 3.0. Consider the local variable declaration and initialization:

TypeName localVariable = value;

Since the type of localVariable can be inferred from the type of value, it is Ok to write code like this:

var localVariable = value; // Compiler infers type of localVariable from type of value.

Here are some samples:

var a = 1;
var b = 1.0;
var c = "Mark";
var d = null as Uri;
var e = default(IEnumerable<Person>);
var f = File.ReadAllLines(filePath);
var g = f.Length;

They are identical to:

int a = 1;
double b = 1.0;
string c = "Mark";
Uri d = null;
IEnumerable<Person> e = default(IEnumerable<Person>);
string[] f = File.ReadAllLines(filePath);
int g = f.Length;

Please notice that type inference must be applied to local variables declaration and initialization statement. The following cannot be compiled:

var a; // Compiler cannot infer the type of a.
var b = null; // Compiler cannot infer the type of b.
private var Func() // Compiler cannot infer the type of return value.
{
throw new NotImplementedException();
}
private void Action(var paramter) // Compiler cannot infer the type of parameter.
{
throw new NotImplementedException();
}

var vs. explicit typing#

Sometimes the “var” keyword seems somewhat convenient:

Dictionary<string, IEnumerable<Person>> dictionary1 = GetDictionary();
var dictionary2 = GetDictionary();

But for consistency, It is a good practice to use explicit type when possible, use var when needed, like anonymous type. This entire tutorial follows this.

var vs. dynamic#

C# 4.0 introduces another keyword dynamic. var is totally different from dynamic:

  • var is for implicit typed local variables, which works at compiled time
  • dynamic is like System.Object. Compiler allow any operation on a dynamic object. Exception is thrown at runtime if operation is invalid..

The above invalid var examples can be compiled by replacing var with dynamic:

dynamic a; // object a;
dynamic b = null; // object b = null;
private dynamic Func() // private object Func()
{
throw new NotImplementedException();
}
private void Action(dynamic paramter) // private void Action(object paramter)
{
throw new NotImplementedException();
}

Immutable anonymous type#

This feature provides a way to create an instance without specifying the type name:

var dixin = new
{
Name = "Dixin",
Age = 30
};

Since the type name is unknown at design time, this is called a anonymous type. At compile time, the type definition will be generated:

[CompilerGenerated]
[DebuggerDisplay(@"\{ Name = {Name}, Age = {Age} }", Type = "<Anonymous Type>")]
internal sealed class AnonymousType<TName, TAge>
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly TName nameBackingField;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly TAge ageBackingField;
[DebuggerHidden]
public AnonymousType(TName name, TAge age)
{
this.nameBackingField = name;
this.ageBackingField = age;
}
public TAge Age { get { return this.ageBackingField; } }
public TName Name { get { return this.nameBackingField; } }
[DebuggerHidden]
public override bool Equals(object value)
{
AnonymousType<TName, TAge> anonymous = value as AnonymousType<TName, TAge>;
return anonymous != null
&& EqualityComparer<TName>.Default.Equals(this.nameBackingField, anonymous.nameBackingField)
&& EqualityComparer<TAge>.Default.Equals(this.ageBackingField, anonymous.ageBackingField);
}
[DebuggerHidden]
public override int GetHashCode()
{
int num = 0x7d068cce;
num = (-1521134295 * num) + EqualityComparer<TName>.Default.GetHashCode(this.nameBackingField);
return ((-1521134295 * num) + EqualityComparer<TAge>.Default.GetHashCode(this.ageBackingField));
}
[DebuggerHidden]
public override string ToString()
{
StringBuilder builder = new StringBuilder();
builder.Append("{ Name = ");
builder.Append(this.nameBackingField);
builder.Append(", Age = ");
builder.Append(this.ageBackingField);
builder.Append(" }");
return builder.ToString();
}
}

It is atomic/immutable type. And the instantiation code s compiled to constructor call:

AnonymousType<string, int> dixin = new AnonymousType<string, int>("Dixin", 30);

However, at design time, the type definition is not generated yet, this is why var must be used.

Reuse type definition#

Anonymous type are reused by 2 anonymous instantiation if they have:

  • the same number of properties
  • the same names of properties
  • the same order of property
  • the same types of properties

For example:

[TestMethod()]
public void ReuseAnonymousType()
{
var anna = new { Name = "Anna", Age = 18 };
var bill = new { Name = "Bill", Age = 19 };
Assert.AreSame(anna.GetType(), bill.GetType()); // Passes.
}

Equality#

Compiler also generates a override of object.Equals(), two anonymous objects are equal if:

  • they are of the same anonymous type
  • their each property’s value are equal
[TestMethod()]
public void AnonymousObjectEquality()
{
Assert.AreEqual(
new { Name = "Dixin", Age = 30 },
new { Name = "Dixin", Age = 30 }); // Passes.
}
Understanding C# Features (3) Implicit Type and Immutable Anonymous Type
https://dixin.github.io/posts/understanding-csharp-features-3-implicit-type-and-immutable-anonymous-type/
Author
Dixin
Published at
2009-11-26
License
CC BY-NC-SA 4.0