For a C # binary, where is the anonymous type information stored?

I did an experiment in C #, first created a class library called ClassLibrary1 with the 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 the namespace information generated by the IDE. Then I created a console app with below code (also remote namespace) referencing 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 result shows that the 2 anonymous classes defined in the class library and the console application have the same class type.

My question is, how does a C # binary store type information for all the classes it contains? If it is stored in a global location, when the exe is built with a dll reference, there are 2 identical data of anonymous type, 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 the rule to look up type information to create real objects? 

      

Seems a little confusing in my example. Thank.

+3


source to share


3 answers


I removed the namespace information

Unnecessary. Anonymous types for assembly are generated in the same namespace, namely empty.

Also, see C # spec 7.6.10.6 Anonymous Object Creation Expressions:

Within the same program, two anonymous object initializers that set a sequence of properties of the same compile-time names and types in the same order will instantiate the same anonymous type.



Vaguely, "program" here means "build". So:

How does a C # binary store type information for all the classes it contains? If it is stored in a global location when the exe is built with dll reference there are 2 identical data of anonymous type

This is correct, but the types are unique to each assembly. They can have the same type name because they are in a different assembly. You can see this by typing m.GetType().AssemblyQualifiedName

which will contain the assembly name.

+3


source


It is possible in a .NET assembly to have duplicate names because metadata elements (classes, fields, properties, etc.) are bound internally using a numeric metadata token, not by name

Although the use of duplicate names is limited in ECMA-335 (except for a few special cases), this feature is used by several obfuscators, and possibly compilers in cases where the name of the metadata item (class in your case) is not directly displayed in user code.



EDIT: CodeCaster is right with his answer, the names are in different assemblies in your case, hence the names are duplicated. While I believe my point is that duplicate names in the same assembly are valid, it might not apply to this particular question.

+1


source


(Note: I am using a simple reverse character here where there is a severe accent in the code, because it has special meaning in markdown method and they look the same. This may not work in all browsers).

My question is, how does a C # binary store type information for all the classes it contains?

Likewise, it preserves any other class. There is no such thing as an anonymous type in .NET, this is what C # (and other .NET languages) provide by compiling on what, at the CIL level, is a perfectly normal class with a perfectly normal name; because at the CIL level there is nothing special about the name <>f__AnonymousType‵2[System.String,System.Int64]

, although its illegal name in C #, VB.NET and many other languages ​​has the advantage of avoiding direct use that would be inappropriate.

If it is stored in a global location, when the exe is built with a dll reference, there are 2 identical data of an anonymous type.

Try changing Console.WriteLine(m.GetType())

to Console.WriteLine(m.GetType().AssemblyQualifiedName)

and you will see that they are not of the same type.

Is duplicate names a mistake to avoid?

No, because the CIL uses AssemblyQualifiedName when it is associated with classes from other assemblies.

If not a bug, as I tested, how can a C # binary duplicate type information?

The error is not in what you looked at, but in the way you looked at it. No duplication.

And at runtime, what's the rule to look for type information to create real objects?

The type is compiled directly into calls, with the search being performed at this point. Consider your f()

:

public static void f()
{
  var m = new { m_s = "abc", m_l = 2L };
  Console.WriteLine(m.GetType());
}

      

This is compiled for two things. The first is an anonymous type, which is included in the list of anonymous type definitions in the assembly, and they are all compiled into an equivalent:

internal class SomeImpossibleName<M_SType, M_LType>
{
  private readonly M_SType _m_s;
  private readonly M_LType _m_l;
  public SomeImpossibleName(M_SType s, M_LType l)
  {
    _m_s = s;
    _m_l = l;
  }
  public M_SType m_s
  {
    get { return _m_s; }
  }
  public M_LType m_l
  {
    get { return _m_l; }
  }
  public override bool Equals(object value)
  {
    var compareWith = value as SomeImpossibleName<M_SType, M_LType>;
    if(compareWith == null)
      return false;
    if(!EqualityComparer<M_SType>.Default.Equals(_m_s, compareWith._m_s))
      return false;
    return EqualityComparer<M_LType>.Default.Equals(_m_l, compareWith._m_l);
  }
  public override int GetHashCode()
  {
    unchecked
    {
      return (-143687205 * -1521134295 + EqualityComparer<M_SType>.Default.GetHashCode(_m_s))
      * 1521134295 + EqualityComparer<M_LType>.Default.GetHashCode(_m_l);
    }
  }
  public override string ToString()
  {
    return new StringBuilder().Append("{ m_s = ")
      .Append((object)_m_s)
      .Append(", m_l = ")
      .Append((object)_m_l)
      .Append(" }")
      .ToString();
  }
}

      

Some notes here:

  • This uses a generic type to keep the compiled size in case you had a bunch of different classes with m_s

    and then m_l

    different types.
  • This allows a simple but reasonable comparison between objects of the same type, without whom GroupBy

    , and Distinct

    will not work.
  • I called it by SomeImpossibleName<M_SType, M_LType>

    real name <>f__AnonymousType0<<m_s>j__TPar, <m_l>j__TPar>>

    . That is, not only the main part of the name is not possible in C #, but also the names of the type parameters.
  • If you have two methods that each execute new Something{ m_s = "abc", m_l = 2L }

    , they will both use that type.
  • The constructor is optimized. While C#

    invocation is usually the var x = new Something{ m_s = "abc", m_l = 2L }

    same as invocation var x = new Something; x.m_s = "abc"; x.m_l = 2L;

    , the code generated to do this with an anonymous type is effectively equivalent var x = new Something("abc", 2L)

    . This has a performance advantage, but it is more important that anonymous types are immutable, although the form of the constructor used only works on named types if they are mutable.

The following CIL for the method is also used:

.method public hidebysig static void f () cil managed 
{
  .maxstack 2
  .locals init
  (
  [0] class '<>f__AnonymousType0`2'<string, int64>
  )

  // Push the string "abc" onto the stack.
  ldstr "abc"

  // Push the number 2 onto the stack as an int32
  ldc.i4.2

  // Pop the top value from the stack, convert it to an int64 and push that onto the stack.
  conv.i8

  // Allocate a new object can call the <>f__AnonymousType0`2'<string, int64> constructor.
  // (This call will make use of the string and long because that how the constructor is defined
  newobj instance void class '<>f__AnonymousType0`2'<string, int64>::.ctor(!0, !1)

  // Store the object in the locals array, and then take it out again.
  // (Yes, this is a waste of time, but it often isn't and so the compiler sometimes adds in these
  // stores).
  stloc.0
  ldloc.0

  // Call GetType() which will pop the current value off the stack (the object) and push on
  // The result of GetType()

  callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()

  // Call WriteLine, which is a static method, so it doesn't need a System.Console item
  // on the stack, but which takes an object parameter from the stack.
  call void [mscorlib]System.Console::WriteLine(object)

  // Return
  ret
}

      

Now something needs to be noted here. Notice how all the calls to the methods defined in the assembly mscorlib

. All assembly calls use this. Just like all classes of classes in assemblies. This way, if two assemblies have a class <>f__AnonymousType0‵2

, they will not cause a collision: internal calls will use <>f__AnonymousType0‵2

, and calls to another assembly will use [Some.Assembly.Name]<>f__AnonymousType0‵2

so that there is no collision.

Another note is this newobj instance void class '<>f__AnonymousType0‵2'<string, int64>::.ctor(!0, !1)

, which is the answer to your question: "And at runtime, what's the rule to look for type information to create real objects?" It is not seen at runtime at all, but the call to the appropriate constructor is determined at compile time.

Conversely, you have nothing to prevent you from having non-anonymous types with the same name in different assemblies. Add an explicit reference to mscorlib default console application project and change his alias global

to global, mscrolib

, then try the following:

namespace System.Collections.Generic
{
  extern alias mscorlib;
  public class List<T>
  {
    public string Count
    {
      get{ return "This is a very strange "Count", isn’t it?"; }
    }
  }
  class Program
  {
    public static void Main(string[] args)
    {
      var myList = new System.Collections.Generic.List<int>();
      var theirList = new mscorlib::System.Collections.Generic.List<int>();
      Console.WriteLine(myList.Count);
      Console.WriteLine(theirList.Count);
      Console.Read();
    }
  }
}

      

As long as there is a name clash System.Collections.Generic.List

, usage extern alias

allows us to specify which assembly the compiler should look for, so we can use both versions next to each other. Of course we wouldn't want to do this, and there is a lot of hassle and confusion, but compilers cannot get confused or confused in the same way.

0


source







All Articles