2009-11-25

<转>c#类的成员初始化顺序

C#作为一种纯面向对象的话言,为它编写的整个代码里面到处都离不开对象。一个对象的完整的生命周期是从开始分配空间到初始化,到使用,最后是销毁,使用的资源被回收。要想真正写出面高质量的代码,我们就得对这期间每一个阶段是怎么样一个状态,framework都做了些什么,我们又能够做些什么都要有些了解才行。
  一般来说大部分程序员对于一个创建好了的对象怎么使用都是比较清楚的,所以本文也就不想就这一部分做太多的说明,重点就集中开对象的创建和销毁这两个阶段,这也是程序员最容易范错误的阶断。本文首先来讲一讲对象成员的初始化,至于对象的释放和销毁,我想放到另外一篇文章里去讲。虽然本文是以C#2005 为例的,但推而广之,对于其它的基于CLS规范的语言应该也是一样的。
首先我们来看看引用类型的成员初始化过程
  我们来看一个例子吧 
class Program
{
static void Main(string[] args)
    {
         DriveB d = new DriveB();
     }
}
class BaseA
{
static DisplayClass a = new DisplayClass("基类静态成员初始化");
     DisplayClass BaseA_c = new DisplayClass("基类实例变量BaseA_c初始化");
public BaseA()
    {
         Console.WriteLine("基类构造方法被调用");
     }
}
class DriveB : BaseA
{
static DisplayClass DriveB_b = new DisplayClass("继承类静态成员DriveB_b初始化");
//static BaseA DriveB_a = new BaseA();
     DisplayClass DriveB_c = new DisplayClass("继承类实例变量DriveB_c初始化");
public DriveB()
    {
         Console.WriteLine("继承类构造方法被调用");
     }
}
class DisplayClass
{
public DisplayClass(string diplayString)
    {
         Console.WriteLine(diplayString);
         Console.WriteLine();
     }
}

程序动行的结果是:
继承类静态成员DriveB_b初始化
继承类实例变量DriveB_c初始化
基类静态成员初始化
基类实例变量BaseA_c初始化
基类构造方法被调用
继承类构造方法被调用
得出初始化顺序结论: 
1)继承类静态成员变量初始化 
2)继承类实例变量初始化 
3)基类静态静态成员变量初始化 
4)基类实例变量初始化 
5)基类构造方法调用 
6)继承类构造方法调用。 
  好像结果和JAVA的有点不一样啊, 有点混乱的感觉,搞不懂M$为什么要让初始化按这样的顺序执行,像JAVA那样严格的从基类到派生类多好呀.上例的运行结果说明, 构造函数这么这个和我们通常思路执行的顺序还是有一定的差别.对于实例成员初始化,基本上就是以下步骤执行:
1 类的对象初始化大体顺序上实例成员赋值到构造函数
2 成员赋值初始化按照由子类到父类的顺序
3 构造函数的初始化按照由父类到子类的顺序
从这里我们有一点需要注意的是,因为成员赋值初始化是从子类到父类的,所以在子类的成员赋值初始化的过程中,不要引用父类定义的成员,因为这个时候父类成员还没有开始初始化.需要说明一点的是C#在创建对象的第一步分配内存完成后会动把所有实例成员变量初始化成变量的默认值,例如整型就是0,引用类型就是null.然后才开始进行成员变量初始化的过程.C#并没有提供类似于C++构造函数中成员特殊的初始化方式:
public constructor(int a)i_a(a){}
估计是因为分配内存和初始化的严格分离,以及反射创建对象的需要,而且也不像C++那样追求的是extreme效率的原因吧;而且就像是以前看到有人说过,再好的语法级别的优化都不能改变写得烂的代码带来的效率低下.
  我们知道,C#里面的静态成员初始化不同于C++的静态成员初始化.C#里的静态成员只会在必要的时候,确切的说是在第一次访问该类的时候才会进行静态成员的初始化.这样做也是有一定道理的,一是减少了内存的开销,再就是加快了程序集启动的时间,很难想像多一个比较费时的静态初始化在程序启动的时候就一一进行,那样的等待会是比较痛苦的.而且大部分时间我们都只是使用一个程序集里面很少的一部分类,如果把程序集里面所有的类不管三七二十一都预先进行初始化的话,对内存和时间的浪废还是比较大的.
  了解了静态成员初始化的时机,就引出了另外一个问题,如果两个类相互间引用,比如A类的静态初始化里引用到了B类,B类的静态
初始化里又引用到了A类,这个时候又会出现什么样的结果呢,还是用例子还说明吧,请看下面这段代码:

using System;
class A
{
public static int X;
static A(){
         X=B.Y+1;
      }
}
class B
{
public static int Y=A.X+1;
static B(){}
static void Main(){
              Console.WriteLine("X={0},Y={1}",A.X,B.Y);
      }
}

产生的输出结果是什么?
一般来说静态声明赋值语句先于静态构造函数执行,没有赋值的类成员声明会被初始化成该类型的默认值,也就是说
public static int X;
public static int Y=A.X+1;
比各自所在的静态构造函数先执行,前一句X没有赋值,默认就是0,后一句的Y在没有赋值之前也是0,赋值后就是A.X+1的值。
类的静态初始化包括成员变量的声明赋值,静态构造函数的执行。
静态初始化只有在类第一次被访问的时候才执行,而且是优先于第一次访问该类的代码执行
因为Main函数在class B中,所以程序先执行的是上面的第二条语句,声明一个Y,再给Y赋值
在赋值的时候又用到了A类中的X静态,当第一次访问A.X的时候,会先调用A类的静态构造函数,这里执行赋值X=B.Y+1,而重新去访问B类的成员,因为前面说的静态初始化只有第一次被访问的时候会执行,所以再次访问B类的时候不会重复进行静态初始化的。这时会因为前一次初始化还未完成,特别是B.Y还没有赋值完成,所以根据上面说的,B.Y现在处理只是声明完成的状态,所以现在B.Y的值就是0,相应的得到的X的值就是1了,在A类的静态构造函数执行完成的时候,程序会再回到B中Y的赋值语句上来,这时候得到的A.X的值就是1,而Y赋值完成后,此时值就变成了2了
因此最终输出的结果就是X=1,Y=2
对于引用类型成员的初始化说了这么多还是总结一下吧.C#中初始化变量(包括实例成员变量和静态成员变量)可以采用成员声明的地方赋值的方式,也可以采用构造函数的方式.我个人在使用实例对象的时候比较推荐采用构造函数的方式,因为构造函数赋值的方式执行的顺序是从父类到子类,这种顺序避免了子类成员变量的初始化过程引用了未赋值的父类成员变量.而且在构造函数中初始化变量可以采用更多的语句块,更多的判断逻辑来初始化,甚至可以加上结构化异常处理try{}catch{}来处理异常信息,远比单单一个赋值语句来得灵活.不过对于简单的内置基本类型(如int,Enum,string等)就无所谓在哪里进行初始化了.
  以上是引用类型的初始化过程,值类型(这里主要是指的结构类型)的静态初始化和引用类型的完全一致.C#的结构类型是有构造函数的(记得C++里面结构也貌似可以声明构造函数),而实例成员的初始化因为结构没有派生的功能,所以在这方面反而比较简单.但是因为值类型始终是不能为空的,一旦声明就必须要分配相应的内存空间,有了内存空间当然是要首先进行初始化的了,这都是为了保证值类型的有效性吧.这个过程是由Framework来完成的,我们自己是没有办法写代码来控制.因此Framework自己在初始化调用构造函数的时候当然就需要对自己要调用的构造函数的参数作个统一的约定,最简单的就是无参构造函数了.所以在C#的每个结构里都默认隐含了一个无参的构造函数,程序员自己可以重载构造函数,但是不能声明自己的无参构造函数(这个是被Framework占用了的).
  有很多刚从C++转到C#的程序员在使用引用类型作为函数的临时变量的时候还能认识到在使用之前需要new一下创建实例再使用,但是在使用结构作为函数的临时变量的时候就喜欢声明后直接拿来使用,问起他们的时候总是说结构是值类型,值类型是存在栈上的,声明后就直接可以使用了.先不论这句话是不是正确的(关于C#中值类型和引用类型到底存在什么地方有时间以后一定写一篇文章专门讨论一下).首先按C#编程规范值类型同样是需要进行成员变量的封装的,很多值类型在声明后就不能够改变,而只声明一个结构体不赋值的话相当于是调用的默认的构造函数,而通常这个默认的构造函数对于我们来说是没有什么意义的.所以得到的值也是没有太大的用处,除非你是想用作out参数所实参,真正用到的时候还得另外赋值.所以当你这样使用结构体的时候,C#编译器会警告你,这个变量只是声明了没有赋值(其实是相当于有一个值,但是没有意义).其实变量使用之前赋值这也是一个很好的习惯,C++里面虽然直接声明了就可以用,但是一般也会在使用之前先ZeroMemory一下,这其实也是相当于初始化了结构体吧,唯一的区别是不需要重新分配空间.

 

// first all the initializers run in order from derived to base, 
// and then all the constructor bodies run in order from base to derived.

http://blogs.msdn.com/ericlippert/archive/2008/02/15/why-do-initializers-run-in-the-opposite-order-as-constructors-part-one.aspx

2009-11-24

静态构造,单例模式,和beforefieldinit

http://smartypeeps.blogspot.com/2007/03/beforefieldinit.html

"beforeFieldInit" is a special flag marked by compiler to the types which doesn't have static constructor.

This special flag tells "calling a static method does not force the system to initialize the type." Means that calling a static method does not make sure the static variables are initialized in the type.

public class ResourceClass

{

private static StreamWriter sw = new StreamWriter();

public static StreamWriter GetInstance()

{

return sw;

}

}

In the above type it is not guranteed that when you call GetInstance the static variable "sw" would be created. Because of the reason the class is marked as "beforeFieldInit".

public class ResourceClass
{
private static StreamWriter sw = new StreamWriter();

static ResourceClass() { };
public static StreamWriter GetInstance()
{
return sw;
}
}
In the above type it is guranteed that when you call GetInstance the static variable "sw" would be created. Because of the reason the class contains "static constructor".

Properties of static constructor

  • Static constructors are not inherited, and cannot be called directly.

The exact timing of static constructor execution is implementation-dependent, but is subject to the following rules:

  1. The static constructor for a class executes before any instance of the class is created.

  2. The static constructor for a class executes before any of the static members for the class are referenced.

  3. The static constructor for a class executes after the static field initializers (if any) for the class. The static constructor for a class executes, at most, one time during a single program instantiation.

  4. The order of execution between two static constructors of two different classes is not specified.

But CLI does insist that all of the field variables will be initialized as soon as one of the static fields is accessed. So if we are depending on some side effects based on static variable initialization, then we may be waiting for a long time that to happen.

---------------------------------------------------------------

http://www.yoda.arachsys.com/csharp/singleton.html

public sealed class Singleton


Fourth version - not quite as lazy, but thread-safe without using locks



{
static readonly Singleton instance=new Singleton();

// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Singleton()
{
}

Singleton()
{
}

public static Singleton Instance
{
get
{
return instance;
}
}
}


As you can see, this is really is extremely simple - but why is it thread-safe and how lazy is it? Well, static constructors in C# are specified to execute only when an instance of the class is created or a static member is referenced, and to execute only once per AppDomain. Given that this check for the type being newly constructed needs to be executed whatever else happens, it will be faster than adding extra checking as in the previous examples. There are a couple of wrinkles, however:




  • It's not as lazy as the other implementations. In particular, if you have static members other than Instance, the first reference to those members will involve creating the instance. This is corrected in the next implementation.


  • There are complications if one static constructor invokes another which invokes the first again. Look in the .NET specifications (currently section 9.5.3 of partition II) for more details about the exact nature of type initializers - they're unlikely to bite you, but it's worth being aware of the consequences of static constructors which refer to each other in a cycle.


  • The laziness of type initializers is only guaranteed by .NET when the type isn't marked with a special flag called beforefieldinit. Unfortunately, the C# compiler (as provided in the .NET 1.1 runtime, at least) marks all types which don't have a static constructor (i.e. a block which looks like a constructor but is marked static) as beforefieldinit. I now have a discussion page with more details about this issue. Also note that it affects performance, as discussed near the bottom of this article.



One shortcut you can take with this implementation (and only this one) is to just make instance a public static readonly variable, and get rid of the property entirely. This makes the basic skeleton code absolutely tiny! Many people, however, prefer to have a property in case further action is needed in future, and JIT inlining is likely to make the performance identical. (Note that the static constructor itself is still required if you require laziness.)



Fifth version - fully lazy instantiation



public sealed class Singleton
{
Singleton()
{
}

public static Singleton Instance
{
get
{
return Nested.instance;
}
}

class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}

internal static readonly Singleton instance = new Singleton();
}
}


Here, instantiation is triggered by the first reference to the static member of the nested class, which only occurs in Instance. This means the implementation is fully lazy, but has all the performance benefits of the previous ones. Note that although nested classes have access to the enclosing class's private members, the reverse is not true, hence the need for instance to be internal here. That doesn't raise any other problems, though, as the class itself is private. The code is a bit more complicated in order to make the instantiation lazy, however.

Win 7 下tortoisesvn右键失效的解决办法

最近安装了win7,发现在文件夹内点击右键,tortoisesvn相关的菜单都没有了,只有在文件夹上火文件上点击才出来,非常不方便,原来是因为project所在的文件夹在Libraries下,解决方法也很简单,从libraries里删除proj所在的顶层文件夹(其实只是快捷方式)即可,或者安装tortoisesvn 的nightly版本

http://groups.google.com/group/tortoisesvn/browse_thread/thread/faa0a1bfeae08e5

2009-11-17

值类型和引用类型

最初的理解:值类型传递值,引用类型传递引用,对于引用类型,加不加ref关键字是没有区别的

值类型,传递值:
static void Main(string[] args)
{
int x = 7;
Console.WriteLine(x.ToString());
Change(x);
Console.WriteLine(x.ToString());
Console.ReadLine();
}

private static void Change(int x)
{
x = 8;
}

Change 方法不会改变x的值,输出为:
7
7

引用类型传递引用:

class Program
{
static void Main(string[] args)
{
MyClass mc = new MyClass();
mc.x = 7;
Console.WriteLine(mc.x.ToString());
Change(mc);
Console.WriteLine(mc.x.ToString());

Console.ReadLine();
}

static void Change(MyClass mc)
{
mc.x = 6;
}
}

class MyClass
{
public int x;
}

输出为:
7
6

但是下面的输出什么呢?

static void Main(string[] args)
{
string x = "hello";
Console.WriteLine(x);
Change(x);
Console.WriteLine(x);

Console.ReadLine();
}

static void Change(string x)
{
x = "world";
}

答案是:都是hello。此例经常被用作面试题。string是引用类型无疑,方法传递的也是引用,但是x的值却没有改变,此例经常用来说明string类型的特殊性,不可变性,真的是这样么?下面的程序片段又输出什么?

static void Main(string[] args)
{
MyClass mc = new MyClass();
mc.x = 6;
Console.WriteLine(mc.x.ToString());
Change(mc);
Console.WriteLine(mc.x);

Console.ReadLine();
}

static void Change(MyClass y)
{
y = new MyClass();
y.x = 7;
}

答案是:
6
6

这个例子和上面的例子本质上是一样的,都是传递的引用的内容,方法只是改变了引用本身(地址),使其指向了新对象的地址,接下来的改变都是在新对象上进行的,也就无法返回到原来的对象,但是如果我们加上ref

MyClass mc = new MyClass();
mc.x = 6;
Console.WriteLine(mc.x.ToString());
Change(ref mc);
Console.WriteLine(mc.x);

Console.ReadLine();
}

static void Change(ref MyClass y)
{
y = new MyClass();
y.x = 7;
}

输出就变成了:
6
7

此处,我们传递的的是引用的引用,也就是引用的地址,可以改变引用本身,也可以改变原对象

参考:

http://www.cnblogs.com/anytao/archive/2007/05/28/must_net_09.html
http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory2B01142006125918PM/csharp_memory2B.aspx
http://msdn.microsoft.com/zh-cn/library/14akc2c7(VS.80).aspx