概念
构造函数是一个特殊方法,在创建类的每次新实例时执行
- 构造函数用于初始化类实例的状态。
 - 如果希望能从类的外部创建类的实例,需要将构造函数声明为public。
 
例
class MyClass
{
    DateTime TimeOfInstantiation;
    public MyClass()
    {
        TimeOfInstantiation = DateTime.Now;
    }
}
特点
- 构造函数的名称和类名相同。
 - 构造函数不能有返回值。
 
带参数的构造函数
- 构造函数可以带参数。参数的语法和其他方法完全相同。
 - 构造函数可以被重载。
 
在使用创建对象表达式创建类的新实例时,要使用new运算符,后面跟着类的某个构造函数。new运算符使用该构造函数创建类的实例。
例
class Class1
{
    int Id;
    string Name;
    public Class1()
    {
        Id = 28;
        Name = "Name";
    }
    public Class1(int val)
    {
        Id = val;
        Name = "Name";
    }
    public Class1(string name)
    {
        Name = name;
    }
    public void SoundOff()
    {
        Console.WriteLine($"Name:{Name},Id:{Id}");
    }
}
class Program
{
    static void Main(string[] args)
    {
        Class1 a = new Class1(),
            b = new Class1(7),
            c = new Class1("Bill");
        a.SoundOff();
        b.SoundOff();
        c.SoundOff();
        Console.ReadKey();
    }
}
结果
Name:Name,Id:28
Name:Name,Id:7
Name:Bill,Id:0
默认构造函数
如果在类的声明中没有显式地提供实例构造函数,那么编译器会提供一个隐式的默认构造函数
特征
- 没有参数
 - 方法体为空
 
如果你为类声明了任何构造函数,那么编译器将不会为该类定义默认构造函数
静态构造函数
构造函数可以声明为static
规则
初始化类级别的项
- 在引用任何静态成员之前
 - 在创建类的任何实例之前
 
静态构造函数与实例构造函数的关系
相同
- 静态构造函数的名称必须和类名相同。
 - 构造函数不能返回值。
 
不同
- 静态构造函数声明中使用static关键字。
 - 类只能有一个静态构造函数,而且不能带参数。
 - 静态构造函数不能有访问修饰符。
 
例
class Class1
{
    static Class1() {
        ....
    }
}
在类中使用
- 类既可以有静态构造函数也可以有实例构造函数。
 - 如同静态方法,静态构造函数不能访问所在类的实例成员,因此也不能使用this访问器
 - 不能从程序中显式调用静态构造函数,系统会自动调用它们,在:
- 类的任何实例被创建之前;
 - 类的任何静态成员被引用之前。
 
 
构造函数初始化语句
默认情况下,构造对象时,将调用基类的无参构造函数,由于构造函数可以重载,这样导致基类可能有多个构造函数。
如果希望派生类使用一个指定的基类构造函数而不是无参的构造函数,那么就需要在构造函数初始化语句中指定他
规则
- 第一种形式使用关键字base并指明使用哪一个基类构造函数。
 - 第二种形式使用关键字this并指明应该使用当前类的哪一个构造函数。
 
基类构造函数初始化语句放在冒号后面,冒号紧跟着类的构造函数声明的参数列表。构造函数初始化语句由关键字base和要调用的基类构造函数的参数列表组成。
1.使用base关键字
public MyDerivedClass(int x, string s): base(s, x) {        //base 构造函数初始化语句
}
在基类参数列表中的参数必须在类型和顺序方面与已定的基类构造函数的参数列表相匹配
当声明一个不带构造函数初始化语句的构造函数时,会默认带有base()
2.使用this关键字
public MyDerivedClass(int x, string s): this(s, x) {        //this 构造函数初始化语句
}
对象初始化语句
关键字new后面跟着一个类构造函数及其参数列表组成
对象初始化语句扩展了创建语法,在表达式的尾部放置了一组成员初始化语句
语法
一种形式包括构造函数的参数列表,另一种不包括。
//FieldOrProp = InitExpr 对象初始化语句
new TypeName {FieldOrProp = InitExpr,FieldOrProp = InitExpr...}
new TypeName(ArgList) {FieldOrProp = InitExpr,FieldOrProp = InitExpr...}
例
new Point{X = 5, Y = 6};
规则
- 创建对象的代码必须能够访问要初始化的字段和属性。例如,在之前的代码中x和Y必须是public的。
 - 初始化发生在构造方法执行之后,因此在构造方法中设置的值可能会在之后对象初始化中重置为相同或不同的值。
 
实例
public class Point
{
    public int X = 1;
    public int Y = 2;
}
class Program
{
    static void Main()
    {
        Point pt1 = new Point();
        Point pt2 = new Point {X = 5, Y = 6};
        Console.WriteLine($"pt1.X:{pt1.X},pt1.Y:{pt1.Y}");
        Console.WriteLine($"pt2.X:{pt2.X},pt2.Y:{pt2.Y}");
        Console.ReadKey();
    }
}
结果
pt1.X:1,pt1.Y:2
pt2.X:5,pt2.Y:6
错误示范
public class Point
{
    public int X = 1;
    public int Y = 2;
}
class Program
{
    static void Main()
    {
        Point pt1 = new Point();
        // Point pt2 = new Point {d = 5, f = 6};       //错误 因为d、f在Point里未定义  
        Point pt2 = new Point {X = 5, Y = 6};       //X、Y如果设置为private也会报错
        Console.WriteLine($"pt1.X:{pt1.X},pt1.Y:{pt1.Y}");
        Console.WriteLine($"pt2.X:{pt2.X},pt2.Y:{pt2.Y}");
        Console.ReadKey();
    }
}
析构函数
在类的实例被销毁之前需要清理或释放非托管资源的行为
注意
- 每个类只能有一个析构函数。析构函数不能有参数。
 - 析构函数不能有访问修饰符。
 - 析构函数名称与类名相同,但要在前面加一个波浪符。
 - 析构函数只能作用于类的实例。因此没有静态析构函数。
 - 不能在代码中显式调用析构函数。相反,当垃圾回收器分析代码并认为代码中不存在指向该对象的可能路径时,系统会在垃圾回收过程中调用析构函数。
 
语法
class Class1
{
    public void Class1() {      //构造函数
    }
    ~Class1() {             //析构函数
    }
}
使用原则
- 不要在不需要时实现析构函数,这会严重影响性能;
 - 析构函数应该只释放对象拥有的外部资源;
 - 析构函数不应该访问其他对象,因为无法认定这些对象是否已经被销毁。
 
dispose模式
和析构函数一样,都是用于释放资源
特点
- 包含非托管资源的类应该实现
IDisposable接口,后者包含单一方法Dispose。Dispose包含释放资源的清除代码。 - 如果代码使用完了这些资源并且希望将它们释放,应该在程序代码中调用
Dispose方法。注意,这是在你的代码中(不是系统中)调用Dispose。 - 你的类还应该实现一个析构函数,在其中调用
Dispose方法,以防止之前没有调用该方法。 
总结一下。你想将所有清除代码放到Dispose方法中,并在使用完资源时调用。以防万一Dispose没有调用,类的析构函数也应该调用Dispose。而另一方面如果调用了Dispose,你就希望通知垃圾回收器不要再调用析构函数,因为已经由Dispose执行了清除操作。
析构函数和Dispose代码原则
- 析构函数和Dispose方法的逻辑应该是,如果由于某种原因代码没有调用
Dispose,那么析构函数应该调用它,并释放资源。 - 在Dispose方法的最后应该调用
GC.SuppressFinalize方法,通知CLR不要调用该对象的析构函数,因为清除工作已经完成。 - 在
Dispose中实现这些代码,这样多次调用该方法是安全的。也就是说代码要这样写:如果该方法已经被调用,那么任何后续调用都不会执行额外的工作,也不会抛出任何异常。 
例
  Dispose方法有两个重载:一个是public的,一个是protected的。protected的重载包含实际的清除代码。
  public版本可以在代码中显式调用以执行清除工作。它会调用protected版本。析构函数调用protected版本。
  protected版本的bool参数通知方法是被析构函数或是其他代码调用。这一点很重要,因为结果不同所执行的操作会略有不同。细节如下面的代码所示。
class MyClass : IDisposable
{
    //释放状态
    bool disposable = false;
    //公共Disposable方法
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    //析构函数
    ~MyClass()
    {
        Dispose(false);
    }
    //分解释放
    protected virtual void Dispose(bool disposing)
    {
        if(disposing == false)
        {
            if(disposing == true)
            {
                //释放资源
            }
            //释放非托管资源
        }
        disposing = true;
    }
}
