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. }