Search code examples
c#.nettostringanonymous-types

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

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