Tag: .NET

.NET类的成员变量初始化与构造函数执行的顺序

Posted by – 2009/02/05

.NET类的成员变量初始化与构造函数执行的顺序是怎样的呢?可以通过下面的测试得出。

namespace MSIL.Tests
{
    public class BaseType
    {
        string field = Helper.WriteLine("base type instance field");
        static string staticField = Helper.WriteLine("base type static field");
 
        static BaseType()
        {
            Console.WriteLine("base type cctor");
        }
 
        public BaseType()
        {
            Console.WriteLine("base type ctor");
        }
    }
 
    public class DerivedType : BaseType
    {
        string field = Helper.WriteLine("derived type instance field");
        static string staticField = Helper.WriteLine("derived type static field");
 
        static DerivedType()
        {
            Console.WriteLine("derived type cctor");
        }
 
        public DerivedType()
        {
            Console.WriteLine("derived type ctor");
        }
    }
 
    public class Helper
    {
        public static string WriteLine(string info)
        {
            Console.WriteLine(info);
            return info;
        }
    }
 
    [TestFixture]
    public class BaseTypeTestFixture
    {
        [Test]
        public void TestNewDerivedType()
        {
            var obj = new DerivedType();
        }
    }
}

在执行了TestNewDerivedType这个用例后会在输出中显示如下结果:

derived type static field
derived type cctor
derived type instance field
base type static field
base type cctor
base type instance field
base type ctor
derived type ctor

可以得出其顺序是:

  1. 派生类静态成员变量
  2. 派生类静态构造函数
  3. 派生类实例成员变量
  4. 基类静态成员变量
  5. 基类静态构造函数
  6. 基类实例成员变量
  7. 基类实例构造函数
  8. 派生类实例构造函数

实际上在这个过程中一直到System.Object的构造函数都会被执行。

另见
Essential .NET Volume 1: The Common Language Runtime Page 60 Types and Initialization

什么是PrivateImplementationDetails

Posted by – 2009/01/18

解析一个.NET程序集时发现了一段<PrivateImplementationDetails>{A_GUID}模样的代码,这是.NET编译器对某些指令做得优化,经分析会在如下情况下产生。

优化数组创建

var int1 = new[] { 1, 2, 3, 4, 5, 6 };

在解析上面的代码时,发现其产生了一些由编译器控制的IL代码

IL_0008:  ldc.i4.6
IL_0009:  newarr     [mscorlib]System.Int32
IL_000e:  dup
IL_000f:  ldtoken    field valuetype '<PrivateImplementationDetails>{393AE015-A550-4A61-9BFF-186AFB6A4D12}'/'__StaticArrayInitTypeSize=24' '<PrivateImplementationDetails>{393AE015-A550-4A61-9BFF-186AFB6A4D12}'::'$$method0x60000f8-1'
IL_0014:  call       void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array, valuetype [mscorlib]System.RuntimeFieldHandle)
IL_0019:  stloc.0

如果没有使用初始化器

var int1 = new int[6];

生成的IL代码如下

IL_0008:  ldc.i4.6
IL_0009:  newarr     [mscorlib]System.Int32
IL_000e:  stloc.0

上面是创建值类型数组的情况,如果是引用类型的数组呢?

var obj1 = new object[3];
var obj2 = new[] {new object(), "test", 1};

生成的IL代码如下

IL_0001:  ldc.i4.3
IL_0002:  newarr     [mscorlib]System.Object
IL_0007:  stloc.0
IL_0008:  ldc.i4.3
IL_0009:  newarr     [mscorlib]System.Object
IL_000e:  stloc.2
IL_000f:  ldloc.2
IL_0010:  ldc.i4.0
IL_0011:  newobj     instance void [mscorlib]System.Object::.ctor()
IL_0016:  stelem.ref
IL_0017:  ldloc.2
IL_0018:  ldc.i4.1
IL_0019:  ldstr      "test"
IL_001e:  stelem.ref
IL_001f:  ldloc.2
IL_0020:  ldc.i4.2
IL_0021:  ldc.i4.1
IL_0022:  box        [mscorlib]System.Int32
IL_0027:  stelem.ref
IL_0028:  ldloc.2
IL_0029:  stloc.1

从下图可以看到,在产生的.NET程序集中有一个<PrivateImplementationDetails>开头的Module

PrivateImplementationDetails

<PrivateImplementationDetails>{0BAF451E-34D3-4B3B-8567-B578D84A6965}是这个编译器产生的Module的名字,{0BAF451E-34D3-4B3B-8567-B578D84A6965}是这个dll的MVID(Module Version Identifier)。

那么为什么要通过调用System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray这个静态方法来初始化数组呢?通过这位同学试验可以得出结论这是为提高效率而做出的编译时优化。

那什么情况下.NET编译器会产生PrivateImplementationDetails这种机制呢,我还没找到一个完整的列表。除数组外我还发现大的switch表也会产生PrivateImplementationDetails,并简单的试出了启用这种机制的case数量的临界值。

优化case比较多的switch

在代码中加入两个方法SwitchWith6Cases()和SwitchWith7Cases()

public void SwitchWith6Cases()
{
    var param = "a";
    switch (param)
    {
        case "a": break;
        case "b": break;
        case "c": break;
        case "d": break;
        case "e": break;
        case "f": break;
    }
}
 
public void SwitchWith7Cases()
{
    var param = "a";
    switch (param)
    {
        case "a": break;
        case "b": break;
        case "c": break;
        case "d": break;
        case "e": break;
        case "f": break;
        case "g": break;
    }
}

前者生成的代码如下,基本符合C#代码的逻辑

.method public hidebysig instance void  SwitchWith6Cases() cil managed
{
  // Code size       105 (0x69)
  .maxstack  2
  .locals init ([0] string param,
           [1] string CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldstr      "a"
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  stloc.1
  IL_0009:  ldloc.1
  IL_000a:  brfalse.s  IL_0068
  IL_000c:  ldloc.1
  IL_000d:  ldstr      "a"
  IL_0012:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_0017:  brtrue.s   IL_005c
  IL_0019:  ldloc.1
  IL_001a:  ldstr      "b"
  IL_001f:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_0024:  brtrue.s   IL_005e
  IL_0026:  ldloc.1
  IL_0027:  ldstr      "c"
  IL_002c:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_0031:  brtrue.s   IL_0060
  IL_0033:  ldloc.1
  IL_0034:  ldstr      "d"
  IL_0039:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_003e:  brtrue.s   IL_0062
  IL_0040:  ldloc.1
  IL_0041:  ldstr      "e"
  IL_0046:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_004b:  brtrue.s   IL_0064
  IL_004d:  ldloc.1
  IL_004e:  ldstr      "f"
  IL_0053:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_0058:  brtrue.s   IL_0066
  IL_005a:  br.s       IL_0068
  IL_005c:  br.s       IL_0068
  IL_005e:  br.s       IL_0068
  IL_0060:  br.s       IL_0068
  IL_0062:  br.s       IL_0068
  IL_0064:  br.s       IL_0068
  IL_0066:  br.s       IL_0068
  IL_0068:  ret
} // end of method SwitchSample::SwitchWith6Cases

而当switch有超过6个case的时候,所生成的IL代码如下。

.method public hidebysig instance void  SwitchWith7Cases() cil managed
{
  // Code size       189 (0xbd)
  .maxstack  4
  .locals init ([0] string param,
           [1] string CS$4$0000,
           [2] int32 CS$0$0001)
  IL_0000:  nop
  IL_0001:  ldstr      "a"
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  stloc.1
  IL_0009:  ldloc.1
  IL_000a:  brfalse    IL_00bc
  IL_000f:  volatile.
  IL_0011:  ldsfld     class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32> '<PrivateImplementationDetails>{E6F6F304-0E96-4AD2-AF7A-7D08372A387B}'::'$$method0x60000fd-1'
  IL_0016:  brtrue.s   IL_0079
  IL_0018:  ldc.i4.7
  IL_0019:  newobj     instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::.ctor(int32)
  IL_001e:  dup
  IL_001f:  ldstr      "a"
  IL_0024:  ldc.i4.0
  IL_0025:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_002a:  dup
  IL_002b:  ldstr      "b"
  IL_0030:  ldc.i4.1
  IL_0031:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_0036:  dup
  IL_0037:  ldstr      "c"
  IL_003c:  ldc.i4.2
  IL_003d:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_0042:  dup
  IL_0043:  ldstr      "d"
  IL_0048:  ldc.i4.3
  IL_0049:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_004e:  dup
  IL_004f:  ldstr      "e"
  IL_0054:  ldc.i4.4
  IL_0055:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_005a:  dup
  IL_005b:  ldstr      "f"
  IL_0060:  ldc.i4.5
  IL_0061:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_0066:  dup
  IL_0067:  ldstr      "g"
  IL_006c:  ldc.i4.6
  IL_006d:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_0072:  volatile.
  IL_0074:  stsfld     class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32> '<PrivateImplementationDetails>{E6F6F304-0E96-4AD2-AF7A-7D08372A387B}'::'$$method0x60000fd-1'
  IL_0079:  volatile.
  IL_007b:  ldsfld     class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32> '<PrivateImplementationDetails>{E6F6F304-0E96-4AD2-AF7A-7D08372A387B}'::'$$method0x60000fd-1'
  IL_0080:  ldloc.1
  IL_0081:  ldloca.s   CS$0$0001
  IL_0083:  call       instance bool class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::TryGetValue(!0,
                                                                                                                        !1&)
  IL_0088:  brfalse.s  IL_00bc
  IL_008a:  ldloc.2
  IL_008b:  switch     ( 
                        IL_00ae,
                        IL_00b0,
                        IL_00b2,
                        IL_00b4,
                        IL_00b6,
                        IL_00b8,
                        IL_00ba)
  IL_00ac:  br.s       IL_00bc
  IL_00ae:  br.s       IL_00bc
  IL_00b0:  br.s       IL_00bc
  IL_00b2:  br.s       IL_00bc
  IL_00b4:  br.s       IL_00bc
  IL_00b6:  br.s       IL_00bc
  IL_00b8:  br.s       IL_00bc
  IL_00ba:  br.s       IL_00bc
  IL_00bc:  ret
} // end of method SwitchSample::SwitchWith7Cases

这时候在<PrivateImplementationDetails>*模块中会有如下的一个叫做$$method0×60000fd-1的静态字段,辅助switch的case匹配。

.field static assembly class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32> '$$method0x60000fd-1'

通过Dictionary<string,int32>::TryGetValue方法去查找匹配的case。O(1)的效率在case多的情况下比线性O(n)的查找快的多。

A Conversation with Anders Hejlsberg, by Bill Venners with Bruce Eckel(August 4, 2003)

Posted by – 2009/01/05

A Conversation with Anders Hejlsberg,
by Bill Venners with Bruce Eckel
August 4, 2003

These conversation includes 8 parts:

  • In Part I: The C# Design Process, Hejlsberg discusses the process used by the team that designed C#, and the relative merits of usability studies and good taste in language design.
  • In Part II: The Trouble with Checked Exceptions, Hejlsberg discusses versionability and scalability issues with checked exceptions.
  • In Part III: Delegates, Components, and Simplexity, Hejlsberg discusses delegates and C#’s first class treatment of component concepts.
  • In Part IV: Versioning, Virtual, and Override, Hejlsberg explains why C# instance methods are non-virtual by default and why programmers must explicitly indicate an override.
  • In Part V: Contracts and Interoperability, Hejlsberg discusses DLL hell and interface contracts, strong names, and the importance of interoperability.
  • In Part VI: Inappropriate Abstractions, Hejlsberg and other members of the C# team discuss the trouble with distributed systems infrastructures that attempt to make the network transparent, and object-relational mappings that attempt to make the database invisible.
  • In Part VII: Generics in C#, Java, and C++, Hejlsberg compares C#’s generics implementation to Java generics and C++ templates, describes constraints in C# generics, and describes typing as a dial.
  • In Part VIII: CLR Design Choices, Hejlsberg discusses IL instructions, non-virtual methods, unsafe code, value types, and immutables.
  • Java与.NET集合框架的比较

    Posted by – 2008/12/23

      Java 中的实现
    Hash Table Resizable Array Balanced Tree Linked List Hash Table + Linked List
    Interfaces Set HashSet   TreeSet   LinkedHashSet
    List   ArrayList   LinkedList  
    Map HashMap   TreeMap   LinkedHashMap

    与之对比

      C5中的实现
    Hash Table Resizable Array Balanced Tree Linked List Hash Table + Linked List
    Interfaces ICollection HashSet   TreeSet   HashedLinkedList
    IList   ArrayList   LinkedList  
    IDictionary HashDictionary   TreeDictionary   ???LinkedHashMap

    java.util.Collection<T>对应 C5中的C5.ICollection

    C5只读模式的集合

    GuardedCollection<T>
    GuardedList<T>
    GuardedDictionary<K,V>

    C5在线文档,其中有很好的注释讲解各种数据结构。在线版本可能不完整,但很方便。更多信息请参见C5项目主页

    使用.NET泛型集合(Generic Collection)

    Posted by – 2008/12/23

    StackOverflow上看到一个关于泛型类型检查的问题
    定义一个泛型类时,可以使用where关键字对初始化时用做类型参数(type arguments)的类型加以约束(constraints )。如果不符合约束则抛出编译时错误(compile-time error)。
    支持的约束有:

    约束 描述
    where T : struct 类型参数必须是值类型。除了Nullable
    where T : class 类型参数必须是引用类型,包括类、接口、委托和数组。
    where T : new() 类型参数必须包含一个公共的无参构造函数,这个约束必须放在所有的其他约束之后。
    where T : <base_class_name> 类型参数必须是此基类或者此基类的派生类
    where T : <interface_name> 类型参数必须实现指定接口,可以指定多接口,或者泛型接口。
    where T : U T的类型参数必须是U,或者U的类型参数派生类。所谓裸类型约束(naked type constraint)。

    .NET泛型支持,泛型参数,泛型接口,泛型方法等。Code Project上一篇讲解.Net泛型集合的文章

    C5项目
    CollectionBase应该是Java中的Set,是众多集合的基类。

    Java集合与C5集合对比

    Java中的 C5中的
    java.util.Set C5.CollectionBase
    java.util.HashSet C5.HashSet
    java.util.List C5.IList
    java.util.TreeSet C5.TreeSet
    java.util.Collection C5.ICollection
    java.util.Map C5.IDictionary

    使用1:

    Set<Entry<VALUE>> newHead = new HashSet<Entry<VALUE>>();

    使用2:

    Map<KEY, Set<Entry<VALUE>>> head = new HashMap<KEY, Set<Entry<VALUE>>>();
    for (Collection<Entry<VALUE>> entries : head.values())
    {
        // ...
    }

    使用3:

    public static <T> List<T> asList(T... a) {
        return new ArrayList<T>(a);
    }

    在方法中使用参数数组

    Posted by – 2008/12/17

    如需向方法中传入个数不定的数组参数,可使用params关键字。具体使用方法如下。

    private string Concatenate(string separator, params object[] parts)
    {
       System.Text.StringBuilder buffer = new System.Text.StringBuilder();
       string sepValue = "";
       foreach (object o in parts)
       {
          buffer.AppendFormat("{0}{1}", sepValue, o);
          sepValue = separator;
       }
       return buffer.ToString();
    }

    在使用时会是如下的写法:

    string result = Concatenate(" ", "test1", "test2", "test3");

    使用Mono.Cecil读写.NET程序集

    Posted by – 2008/12/10

    使用Reflexil修改.NET程序集
    http://www.codeproject.com/KB/msil/reflexil.aspx

    基于Cecil的项目合集
    http://groups.google.com/group/mono-cecil/web/projects-using-cecil

    DimeCast上一个视频演示通过Cecil向程序集中注入代码MSDN关于IL指令的文档

    Mono相关的项目

    Posted by – 2008/12/08

    有幸在上周五六参加Apache Meetup和Barcamp活动中遇到了Novell公司的Alex Lau。期间他提到了Mono Accecibility这个项目。我在构建NTE时用到了Mono Cecil来读取ECMA IL格式的指令,所以对Mono的相关项目颇有好感。

    所以再次罗列一下网站资源:

    • Cecil – A .net assembly manipulation library
    • Accessibility – aims to enable Windows applications to be fully accessible on Linux
    • Gendarme – rule-based tool to inspect .Net assemblies(which using Cecil)

    一个视频Miguel de Icaza谈Mono

    如果有机会应该参与一些Mono相关的工作。Google也参与了Mono相关的项目

    .NET Testability Explorer

    Posted by – 2008/12/03

    在负责一个Unit Testing on CAB的内部培训项目期间,我阅读了Miško Hevery的一系列文章,他目前在Google担任敏捷教练。我对他的Testability Explorer(简称GTE) 产生了浓厚兴趣。

    虽然Java和.Net世界已经有一些类似工具存在,但GTE这个项目实现功能比较专一,输出结果也直接易懂。相对NDepend强大但相对复杂和FxCop官方但略显强硬,GTE这个项目从形式上来说更有亲和力。所以我和Joe同学在Google Code上开始了一个新项目: .NET Testability Explorer(简称NTE)。
    功能受众是期望对输出的.NET Assembly进行分析的开发人员,报告中提供的信息可以帮助他们对设计进行调整,从而使code base更容易测试和维护。

    计划是,首先实现圈复杂度的度量,然后实现输出类似GTE形式的报告。希望能够在09年农历新年前发出第一个版本。
    目前TE的成本模型支持对全复杂度、全局变量和笛米特法则的计算。

    技术层面可以参考Gendarme这个项目,一个基于Mono.Cecil的代码分析工具。

    Updates 20081221
    项目分成两部分:IL分析,基于规则的成本模型计算

    IL分析:目前IL分析方面的技术调研实现了类型,字段,方法级别的读取。剩下最大的一部分是方法中instruction的decompose,这部分估计占全部的40%工作量。但是我需要从成本模型分析的方向进行分析,确定在decompose过程中所需要收集的信息,所以先完成大概10%遍历所有instruction构建一个草图模型。目标是实现最简单的圈复杂度的计算。
    成本模型:完成大概10%-20%的基础模型构建,支持全复杂度的计算。
    下一步,重构设计使其能支持更多的规则考量,并增强其可扩展性。
    目前从代码量来看完成了TE全部功能的1/6,考虑到TE支持对C++语言的分析,折半其工作量,目前大概完成了全部工作的1/3。

    目标定于在完成一半的时候提交代码到Google Code,并发布第一个可运行的版本。时间定于08年圣诞节期间。新的计划是春节之前,发布第一个可运行的版本,并将代码提交。

    在VS中调试.Net Framework

    Posted by – 2008/12/02

     需要在VS中配置symbol的服务器,详见这里

    Symbol会被缓存在客户端,但是每次加载工程源代码会被重新下载。