I did an experiment in C#, first I created a class library called "ClassLibrary1", with code below:
public class ClassLibrary1
{
public static void f()
{
var m = new { m_s = "abc", m_l = 2L };
Console.WriteLine(m.GetType());
}
}
Note, I removed namespace information generated by IDE. Then I created console application with code below:(also removed namespace) while referring to ClassLibrary1:
class Program
{
static void Main()
{
var m = new {m_s = "xyz", m_l = 5L};
Console.WriteLine(m.GetType());
ClassLibrary1.f();
}
}
I run the program, it prints:
<>f__AnonymousType0`2[System.String,System.Int64]
<>f__AnonymousType0`2[System.String,System.Int64]
Press any key to continue . . .
The output indicates that the 2 anonymous classes defined in class library and console application are having identical class type.
My question is: how does C# binary store its type information for all the classes it contains? If it's stored in a global place, when the exe is built with dll reference, 2 same anonymous type information is there, so
(1) Is name duplication an error that should be avoid?
(2) If not an error like I tested, how could C# binary store duplicate type information?
(3) And in runtime, what's the rule to look up type information to create real objects?
Seems a bit confusing in my example. Thanks.
(Note, I'm using the reversed prime ‵ character here where the grave accent character is in the code, since that has a special meaning in markdown, and they look similar. This may not work on all browsers).
The same way it stores any other class. There is no such thing as an anonymous type in .NET, it's something that the C# (and other .NET languages) provide by compiling to what at the CIL level is a perfectly normal class with a perfectly normal name; because at the CIL level there's nothing special about the name
<>f__AnonymousType‵2[System.String,System.Int64]
though its being an illegal name in C#, VB.NET and many other languages has the advantage of avoiding direct use that would be inappropriate.Try changing your
Console.WriteLine(m.GetType())
toConsole.WriteLine(m.GetType().AssemblyQualifiedName)
and you'll see that they aren't the same type.No, because CIL produced uses the AssemblyQualifiedName if it deals with classes from other assemblies.
The error was not in what you looked at, but in how you looked at it. There is no duplication.
The type gets compiled directly into the calls, with the lookup happening at that point. Consider your
f()
:That is compiled to two things. The first is the anonymous type here goes into a list of definitions of anonymous types in the assembly, and they are all compiled into the equivalent of:
Some things to note here:
m_s
followed by anm_l
of different types.GroupBy
andDistinct
would not work.SomeImpossibleName<M_SType, M_LType>
the real name would be<>f__AnonymousType0<<m_s>j__TPar, <m_l>j__TPar>>
. That is, not only is the main part of the name impossible in C#, but so are the names of the type parameters.new Something{ m_s = "abc", m_l = 2L }
they will both use this type.C#
generally callingvar x = new Something{ m_s = "abc", m_l = 2L }
is the same as callingvar x = new Something; x.m_s = "abc"; x.m_l = 2L;
the code created for doing so with an anonymous type is actually the equivalent tovar x = new Something("abc", 2L)
. This both gives a performance benefit but more importantly allows anonymous types to be immutable even though the form of constructor used only works with named types if they are mutable.Also the following CIL for the method:
Now, some things to note here. Notice how all the calls to methods defined in the
mscorlib
assembly. All calls across assemblies use this. So too do all uses of classes across assemblies. As such if two assemblies both have a<>f__AnonymousType0‵2
class, they will not cause a collision: Internal calls would use<>f__AnonymousType0‵2
and calls to the other assembly would use[Some.Assembly.Name]<>f__AnonymousType0‵2
so there is no collision.The other thing to note is the
newobj instance void class '<>f__AnonymousType0‵2'<string, int64>::.ctor(!0, !1)
which is the answer to your question, "And in runtime, what's the rule to look up type information to create real objects?". It isn't looked up at runtime at all, but the call to the relevant constructor is determined at compile time.Conversely, there's nothing to stop you from having non-anonymous types with the exact same name in different assemblies. Add an explicit reference to mscorlib to a console application project and change its alias from the default
global
toglobal, mscrolib
and then try this:While there's a collision on the name
System.Collections.Generic.List
, the use ofextern alias
allows us to specify which assembly the compiler should look in for it, so we can use both versions side by side. Of course we wouldn't want to do this and its a lot of hassle and confusion, but compilers don't get hassled or confused in the same way.