Поделиться через


Anonymous types unify within an assembly, Part One

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.

Comments

  • Anonymous
    January 23, 2012
    You have mistakes both your versions of M() (Anon1 and C.anon).

  • Anonymous
    January 23, 2012
    Why would anyone want to do this?  Is the goal to make the code entirely unmaintainable?

  • Anonymous
    January 23, 2012
    The comment has been removed

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

  • Anonymous
    January 23, 2012
    And that is because you haven't come across Enumerable.Union() yet?

  • Anonymous
    January 24, 2012
    @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.

  • Anonymous
    January 24, 2012
    @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.

  • Anonymous
    January 24, 2012
    After the first word of the title I though you are going to talk about SOPA....

  • Anonymous
    January 25, 2012
    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.

  • Anonymous
    January 25, 2012
    @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 ;)

  • Anonymous
    January 25, 2012
    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.

  • Anonymous
    January 25, 2012
    @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.

  • Anonymous
    January 26, 2012
    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};}

  • Anonymous
    January 27, 2012
    @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. :)