对于C#binary,存储了匿名类的类型信息?

时间:2015-06-16 08:40:32

标签: c# types binary anonymous

我在C#中做了一个实验,首先我创建了一个名为“ClassLibrary1”的类库,代码如下:

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

注意,我删除了IDE生成的命名空间信息。 然后我用以下代码创建了控制台应用程序:(也删除了命名空间 在引用ClassLibrary1:

class Program
{
    static void Main()
    {
        var m = new {m_s = "xyz", m_l = 5L};
        Console.WriteLine(m.GetType());
        ClassLibrary1.f();
    }
}

我运行程序,打印出来:

<>f__AnonymousType0`2[System.String,System.Int64]
<>f__AnonymousType0`2[System.String,System.Int64]
Press any key to continue . . .

输出表明类库和控制台应用程序中定义的2个匿名类具有相同的类类型。

我的问题是:C#binary如何存储它包含的所有类的类型信息?如果它存储在全局位置,当使用dll引用构建exe时,会有2个相同的匿名类型信息,所以

(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? 

在我的例子中似乎有点混乱。 感谢。

3 个答案:

答案 0 :(得分:3)

  

我删除了名称空间信息

无关。程序集的匿名类型在同一名称空间中生成,即空名称。

此外,请参阅C#规范7.6.10.6匿名对象创建表达式:

  

在同一个程序中,两个匿名对象初始值设定项以相同的顺序指定相同名称和编译时类型的属性序列,这将生成相同匿名类型的实例。

令人困惑,&#34;节目&#34;这意味着&#34;组装&#34;。所以:

  

C#binary如何存储它包含的所有类的类型信息?如果它存储在全局位置,当使用dll引用构建exe时,会有2个相同的匿名类型信息

那是对的,但每个组件的类型都是唯一的。它们可以具有相同的类型名称,因为它们位于不同的程序集中。您可以通过打印m.GetType().AssemblyQualifiedName来查看,其中包括程序集名称。

答案 1 :(得分:1)

.NET程序集中可能有重复的名称,因为元数据项(类,字段,属性等)在内部由数字元数据标记引用,而不是由名称

引用

尽管在ECMA-335中限制使用重复名称(除了几个特殊情况),但是这种可能性被许多混淆器利用,并且可能由编译器利用元数据项的名称(在您的情况下为类)不直接暴露给用户代码

编辑:CodeCaster的答案是正确的,名称存在于您的案例中的不同程序集中,因此名称重复。虽然我相信我在同一个程序集中有重复名称的观点是有效的,但可能不适用于这个特定的问题。

答案 2 :(得分:0)

(注意,我在这里使用了反向素数字符,其中严重重音字符在代码中,因为它在markdown中具有特殊含义,它们看起来相似。这可能不适用于所有浏览器。)< / p>

  

我的问题是:C#binary如何存储它包含的所有类的类型信息?

与存储任何其他类的方式相同。在.NET中没有匿名类型这样的东西,它是C#(和其他.NET语言)通过编译到CIL级别的内容而提供的,具有完全正常名称的完全正常的类;因为在CIL级别,名称<>f__AnonymousType‵2[System.String,System.Int64]并没有什么特别之处,尽管它在C#中是一个非法名称,VB.NET和许多其他语言具有避免直接使用不合适的优势。

  

如果它存储在全局位置,当使用dll引用构建exe时,会有2个相同的匿名类型信息。

尝试将Console.WriteLine(m.GetType())更改为Console.WriteLine(m.GetType().AssemblyQualifiedName),您会发现它们的类型不同。

  

名称重复是否应该避免错误?

不,因为CIL生成使用AssemblyQualifiedName,如果它处理来自其他程序集的类。

  

如果没有像我测试的那样的错误,C#binary如何存储重复类型信息?

错误不是你所看到的,而是你如何看待它。没有重复。

  

在运行时,查找类型信息以创建真实对象的规则是什么?

该类型直接编译到调用中,查找发生在该点。考虑一下您的f()

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

这是编译成两件事。第一个是匿名类型,这里列出了程序集中匿名类型的定义列表,它们都编译成等效的:

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();
  }
}

有些事情需要注意:

  1. 这使用泛型类型,如果你有一堆不同类,m_s后跟m_l不同类型,则可以节省编译后的大小。
  2. 这允许在相同类型的对象之间进行简单但合理的比较,否则GroupByDistinct将不起作用。
  3. 我称之为SomeImpossibleName<M_SType, M_LType>,真实姓名为<>f__AnonymousType0<<m_s>j__TPar, <m_l>j__TPar>>。也就是说,不仅在C#中名称的主要部分是不可能的,而且类型参数的名称也是如此。
  4. 如果您有两种方法,每种方法都new Something{ m_s = "abc", m_l = 2L },则它们都将使用此类型。
  5. 构造函数已经过优化。虽然在C#中通常调用var x = new Something{ m_s = "abc", m_l = 2L }与调用var x = new Something; x.m_s = "abc"; x.m_l = 2L;相同,但使用匿名类型创建的代码实际上等同于var x = new Something("abc", 2L)。这两者都提供了性能优势,但更重要的是允许匿名类型是不可变的,即使构造函数的形式只适用于命名类型(如果它们是可变的)。
  6. 该方法还有以下CIL:

    .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's 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
    }
    

    现在,有些事情需要注意。注意所有对mscorlib程序集中定义的方法的调用。跨程序集的所有调用都使用它。跨程序集的所有类的使用也是如此。因此,如果两个程序集都具有<>f__AnonymousType0‵2类,则它们不会导致冲突:内部调用将使用<>f__AnonymousType0‵2,而对另一个程序集的调用将使用[Some.Assembly.Name]<>f__AnonymousType0‵2,因此不会发生冲突。

    要注意的另一件事是newobj instance void class '<>f__AnonymousType0‵2'<string, int64>::.ctor(!0, !1)这是你问题的答案,“在运行时,查找类型信息以创建真实对象的规则是什么?”。它根本没有在运行时查找,但是在编译时确定对相关构造函数的调用。

    相反,没有什么可以阻止你在不同的程序集中使用完全相同名称的非匿名类型。将mscorlib的显式引用添加到控制台应用程序项目,并将其别名从默认global更改为global, mscrolib,然后尝试:

    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();
        }
      }
    }
    

    虽然名称System.Collections.Generic.List上存在冲突,但extern alias的使用允许我们指定编译器应该查找哪个程序集,因此我们可以并排使用这两个版本。当然,我们不想这样做,而且很多麻烦和混乱,但编译器不会以同样的方式受到麻烦或混淆。