Anonymous types unify within an assembly, Part One

Anonymous types unify within an assembly, Part One

Rate This
  • Comments 14

Back in my last post of 2010 I said that I would do an example of anonymous types unifying within an assembly "in the new year". I meant 2011, but here we are "in the new year" again, so, no time like the present.

The C# specification guarantees you that when you use "the same" anonymous type in two places in one assembly (*) the types unify into one type. In order to be "the same", the two anonymous types have to have the exact same member names and the exact same member types, in the exact same order. (**)

It is easy to see how this works in a method body; you could simply say:

var anon1 = new { X = 1 };
var anon2 = new { X = 2 };
anon2 = anon1;
Console.WriteLine(anon2.X); // 1

And it is easy to see how you could verify this fact with reflection across method bodies within the same assembly:

class C
{
    public static object Anon1() { return new { X = 1 }; }
}
class D
{
    static void M()
    {
        // True:
        Console.WriteLine(C.Anon1().GetType() == (new { X = 2 }).GetType());
    }
}

But... so what? Using reflection in this way seems supremely uninteresting. What would be more interesting is to merge the two examples together:

class C
{
    public static object Anon1() { return new { X = 1 }; }
}
class D
{
    static T CastByExample<T>(T example, object obj)
    {
        return (T)obj;
    }
    static void M()
    {
        var anon1 = CastByExample(new { X = 0 }, C.Anon1());
        var anon2 = new { X = 2 };
        anon2 = anon1;       
        Console.WriteLine(anon1.X); // 1
    }
}

As you can see, you can share instances of an anonymous type around an assembly if you want to. We do not expect that a lot of people will do so; typically if this is the sort of thing you like doing, you'd use either a tuple or a custom-built nominal type. Still, it is not expensive for us to make this guarantee, and it is kind of a nice property.


(*) Technically not exactly true. You could have an assembly made up of many different netmodules compiled at different times that do not know about each other; anonymous types defined in one netmodule will not necessarily unify with those in others.

(**) Next time I'll discuss why we have this latter requirement.

  • You have mistakes both your versions of M() (Anon1 and C.anon).

  • Why would anyone want to do this?  Is the goal to make the code entirely unmaintainable?

  • Brian,

    There are some reasons why you might want to do this, though I'd certainly not add it to my "commonly used tricks" bag. Using this guarantee is rather ugly most of the time, but given Eric's assertion that it wasn't expensive to allow this, I don't see why they shouldn't make it an option.

    Anonymous types very often come out of LINQ queries, and you might want to return a list of some anonymous type from a method. You can do this by saving your anonymous type as an object, then using some judicious generics and typecasts to get it back out.

    public object Foo()

    {

       return new { Month = 1, Year = 2011 };

    }

    public T ToType<T>(object x, T type)

    {

     return (T)x;

    }

    object x = Foo();

    var y = ToType(x, new { Month = 0, Year = 0 });

    Perhaps more usefully, you might want to use an anonymous type in a try/catch/finally block or other scoping situation where you need to "pre-declare" your variable. So you could do, for example:

    var x = new { Month = 0, Year = 0};

    try

    {

     x = dates.Select(x => { x.Month, x.Year }).Where(x => x.Month > 6).First();

    }

    catch

    {

     // whatever

    }

    if (x.Month > 0)

       DoSomethingWith(x);

  • A simpler example with LINQ would be where you want to construct the union of two separate queries.

  • And that is because you haven't come across Enumerable.Union() yet?

  • @Tanveer: Damien's point is that you can have two seperate LINQ queries that both project to the same anonymous type, and then use Enumerable.Union() to merge the results.  If every decleration of an anonymous tpye created a seperate class, this wouldn't be so possible.

  • @Tanveer:

    Consider a system that stores information about a school. You have a function that sends something to all of the responsible adults:

    var parents = from s in students

                         select new {Name = s.ParentName, Address = s.Address};

    var staff = from s in staff

                    select new {Name = s.Name, Address = s.Address };

    var mailingList = parents.Union(staff);

    Without this guarantee, even this wouldn't be allowed.

  • After the first word of the title I though you are going to talk about SOPA....

  • Other than the cost/benefit argument, is there any technical reason why C# couldn't allow something akin to:

       public static var Anon1() { return new { X = 1 }; }

    It seems like the compiler has all the information it needs to infer the "nameless" type of the return argument from Anon1(), yes? ... and this would make it possible to more easily write functions that return anonymous types without the awkwardness or error potential of casting by example.

    While I probably would avoid creating public methods that return "var" types (just like I would avoid public methods that return Tuple<,>) there are cases where for rapid prototyping or internally within a class you want to avoid the effort of creating normative types.

  • @Leo: I think Eric's mentioned previously that the way the c# compiler works is that it first scans through the source, skipping the method bodies, but noting the declarations of the methods etc. Then once it has a full / consistent view of what the classes are and what methods they all have, *then* it compiles the method bodies - the class/method signatures being necessary for that, in order that it can do overload resolution, etc...

    Granted I suppose you could argue the compiler doesn't *have* to do things that way, but still.

    Also consider this code:

    public static var Anon1() { return new { X = Anon2(); }

    public static var Anon2() { return new { X = Anon1(); }

    Not sure I'd like to be a compiler trying to compile that ;)

  • It's rather unfortunate that anonymous types can't be used in function signatures, while certain LINQ providers can't return custom-built nominal types. This leads to creating every object twice -- once to return from LINQ, then copied into a custom nominal type to return from the function.

  • @Leo,

    If the function were public, you would run into problems when the declaring assembly was recompiled.  The compiler would need to guarantee that the name of the anonymous type remained the same across compiles, otherwise any assembly that consumed the anonymous type would break.

    I think you can already come fairly close to what that would accomplish using dynamic anyway; you just wouldn't get intellisense.

  • I've remarked in the past that it'd be handy to have a way to express a "name" for an anonymous type when needed in a pinch. My suggestion was something like this:

    public static @{int X} Anon1() { return new {X = 1};}

  • @ficedule: Excellent points about the inference sequence and potential havoc from unresolvable cyclical references. Thanks.

    @ChrisB: It's true that dynamic can be used in this manner, but there is an overhead to runtime generation of dynamic binders that is undesirable. The beauty of anonymous types is that they perform just as well as their named counterparts.

    I guess I long for C# to get some of the typesystem features that languages like F# and OCaml have, where you rarely have to declare types explicitly and tuples (and algebraic data types) are first-class citizens. :)

Page 1 of 1 (14 items)