How does ToString on an anonymous type work?

I was messing with anonymous types, and I accidentally outputted it onto the console. It looked basically how I defined it.

Here's a short program that reproduces it:

using System;
class Program
{
    public static void Main(string[] args)
    {
        int Integer = 2;
        DateTime DateTime = DateTime.Now;
        Console.WriteLine(new { Test = 0, Integer, s = DateTime });
        Console.ReadKey(true);
    }
}

Now, the output is:

{ Test = 0, Integer = 2, s = 28/05/2013 15:07:19 }

I tried using dotPeek to get into the assembly to find out why, but it was no help.[1] Here's the dotPeek'd code:

// Type: Program
// Assembly: MyProjectName, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// Assembly location: Not telling you! :P
using System;
internal class Program
{
  public static void Main(string[] args)
  {
    Console.WriteLine((object) new
    {
      Test = 0,
      Integer = 2,
      s = DateTime.Now
    });
    Console.ReadKey(true);
  }
}

So not much different, at all.

So how does it work? How does it output it like that?

Notes:

[1]: I forgot to turn on "Show compiler-generated code", that's the reason I didn't get how it works.


Solution 1:

With anonymous objects...

The compiler generates an internal sealed class that models the anonymous type. The anonymous type is immutable; all the properties are read only. That class contains overrides of Equals() and GetHashCode() that implement value semantics. In addition, the compiler generates an override of ToString() that displays the value of each of the public properties.

Source : link

Please, check @Ilya Ivanov answer to see some code about this subject.

Solution 2:

Just to add some code to HuorSwords answer, compiler will generate ToString method for your example, as given below:

public override string ToString()
{
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.Append("{ Test = ");
    stringBuilder.Append((object) this.<Test>__Field);
    stringBuilder.Append(", Integer = ");
    stringBuilder.Append((object) this.<Integer>__Field);
    stringBuilder.Append(", s = ");
    stringBuilder.Append((object) this.<s>__Field);
    stringBuilder.Append(" }");
    return ((object) stringBuilder).ToString();
}

It would be performance inefficient to use reflection here, when you have all required metadata at compile time.

Decompiled using dotPeek, this version may vary depending on used decompiler.

Note: as you also decompiled with dotPeek, try to look at Root Namespace. There you will find something similar to:

[DebuggerDisplay("\\{ Test = {Test}, Integer = {Integer}, s = {s} }", Type = "<Anonymous Type>")]
internal sealed class <>__AnonymousType0<<Test>

This is an example of what the compiled generates, when you define anonymous objects.

Solution 3:

Anonymous types are still fully defined types ... simply: the compiler generates them entirely itself, and you never see the name / implementation (just: it matches the initializer you use in your code).

Actually, the ToString is not mentioned in regards to anonymous types in the specificaction (section 7.6.10.6); it is only required that Equals and GetHashCode work in terms of the properties. The example in the specification ("declares an anonymous type of the form") does not include a ToString override.

The MS compiler adds a property-based implementation of ToString as a courtesty - but also perhaps because the default ToString is the type name, which would by itself be meaningless (it is, after all, anonymous - the type name is pretty horrible to read, and includes generics syntax). Frankly it would be a good idea to only use this for debugging purposes.