class Text1 { public static string tt1 = "1"; //第一执行 public string tt2 = "2";//第三执行 static Text1() { tt1 = "3";//第二执行 } public Text1() { tt2 = "4";//第四执行 } }
1. 引用类型(class)与值类型(strust)的构造函数(实例构造器)
1, 创建一个引用类型的实例时,首先为实例的数据字段分配内存,然后初始对象的附加字段,最后调用实例构造器来设置对象的初始化状态。
2, 实例构造器用于不能被继承,如果没有定义任何构造器,编译器会指定一个默认的无参数构造器,在它的实现中只是简单的调用基类的无参构造器。(编译器不会为值类型生成默认的构造器,而且不能定义无参构造器)
3, 如果派生类没有显示的调用一个基类的构造器,编译器会自动生成对默认的基类构造器的调用,最终调用到Object的构造器。
4, 不要在构造器中调用会影响构造对象的任何虚方法,因为假如在派生类中进行了重写,会调用重写的实现,但在继承层次中,字段没有完全初始化(很好理解,B:A,A的构造函数中调用虚方法Show(),会调用B中重写的Show(),这个时候B没有初始化,容易出错)。
5, 在多构造器的时候不要造定义字段的时候就赋值,这样的话在每一个构造器编译的时候会生成多次初始化字段的代码。如果有几个初始化的实例字段和大量的重载构造器方法,可考虑在单个构造器中执行这些公共的初始化。(值类型不能为字段设置初始值,而且在定义的有参构造器中必须对字段全部赋值)例如下面这种写法
class CLRClassTest
{
private Int32 a = 2;
private string b = "1d";
private double c = 2.22;
public CLRClassTest()
{ }
public CLRClassTest(string a)
{ }
public CLRClassTest(double b)
{}
}
转换成下面这种写法
class CLRClassTest
{
private Int32 a;
private string b;
private double c;
public CLRClassTest()
{
a = 2;
b = "1d";
c = 2.22;
}
public CLRClassTest(string a)
: this()
{ }
public CLRClassTest(double b)
: this()
{ }
}
1.类型构造器
- 类型构造器的作用是设置类型的初始化状态,类型构造器没有参数,写法如下
class SomeRefType
{
static SomeRefType()
{
//SomeRefType被首次访问时,会执行这里的代码
}
}
- 定义类型构造器类似于定义无参实例构造器,区别在于必须标记为static,而且默认为私有,不能设置访问修饰符。
- 类型构造器中的代码只能访问静态字段,并且常规用途就是初始化这些字段,编译器会自动生成一个类型构造器,如下
class SomeRefType
{
private static Int32 x = 5;
}
默认生成如下代码
class SomeRefType
{
private static Int32 x ;
static SomeRefType()
{
x = 5;
}
}
4. 操作符重载方法(operator)
public class CLR操作符重载 { //一元操作符(只有一个参数参与): i++ i-- !i,~ //二元操作符(有2个参数参与):a+a a-a * % /&|^<<>>== //三元操作符 A=="111"?B:C int a,b; public CLR操作符重载(int s1,int s2) { a = s1; b = s2; } public static CLR操作符重载 operator +(CLR操作符重载 c1, CLR操作符重载 c2) { return new CLR操作符重载(c1.a+c2.a,c1.b+c2.b); } public static CLR操作符重载 operator++(CLR操作符重载 c) { return new CLR操作符重载(c.a++, c.b++); } public override string ToString() { return a+"_"+b; } } public class Test_CLR操作符重载 { public static void Test() { CLR操作符重载 c1 = new CLR操作符重载(1, 2); CLR操作符重载 c2 = new CLR操作符重载(5, -1); var c = c1 + c2; Console.WriteLine(c); Console.WriteLine(c1++); Console.ReadKey(); } }
5. 转换操作符(implicit,explicit)
1, 当源类型和目标类型都是基元类型时,编译器自己就知道如何生成转换对象所需要的代码
2, Implicit关键字告诉编译器为了生成代码来调用方法,不需要再源代码中进行显示转换,explicit关键字告诉编译器只有在发现了显示转换时,才调用方法。
3, 在implicit和explicit关键字之后,要指定operator关键字告诉编译器该方法是一个转换操作符,在operator之后,指定对象要转换成什么类型,在圆括号内指定要从什么类型转,注意使用as或者is操作符时候,永远不会调用这些方法
具体使用方法见下面代码。
/// <summary>
/// 转换操作符
/// </summary>
class CRLRational
{
int a;
float b;
public CRLRational(int num)
{ a = num; }
public CRLRational(float num)
{ b = num; }
public int ToInt()
{
return a;
}
public float ToFloat()
{
return b;
}
public static implicit operator CRLRational(int num)
{
return new CRLRational(num);
}
public static explicit operator CRLRational(float num)
{
return new CRLRational(num);
}
public static implicit operator int(CRLRational r)
{
return r.ToInt();
}
public static explicit operator float(CRLRational r)
{
return r.ToFloat();
}
}
调用方法如下
CRLRational r1 = 5;
CRLRational r2 = (CRLRational)2.5f;
CRLRational r2 =2.5f;
//CRLRational r3 = 2.5f as CRLRational;
//无法通过引用转换、装箱转换、取消装箱转换、包装转换或 null 类型转换将类型“float”转换为“CRLRational”
int a = (int)r1;
float b = (float)r2;
6扩展方法(extension method)
扩展方法的规则和原则
1, c#只支持扩展方法,不支持扩展属性,扩展事件,扩展操作等,
2, 扩展方法必须在非泛型的静态类中声明,类名没有限制,至少包含一个参数且第一个参数必须为this标记,且只有第一个参数被this标记
3, 扩展方法必须在顶级静态类中定义,不能再嵌套类中定义。
4, 编译器会检查文件作用域中所有的静态类,并扫描它们的所有静态方法来匹配,为了增强性能,避免找到非你所愿的一个扩展方法,编译器要求Using扩展方法的命名空间。
5, 不能给object类型写扩展方法,避免VS智能感知感知到太多垃圾信息。
6, 潜在风险,假如微软提供了一个你扩展方法相同的方法,会调用微软的方法
编译器查找扩展方法的路径,StringBuilder为例:它先检查StringBuilder的所有基类是否提供相应方法,如果存在就调用,如果没有,则继续检查是否有任何静态类定义的对应静态方法,它的第一个参数是和当前用于调用方法的那个表达式的类型匹配的一个类型,而且这个类型必须用this关键字表示,
下面是一个扩展方法的实例:
public static class CLR扩展方法
{
public static int IndexOf(this StringBuilder sb, Char value)
{
for (int i = 0; i < sb.Length; i++)
{
if (sb[i] == value)
return i;
}
return -1;
}
}
StringBuilder sb = new StringBuilder("ffsafsadyaerbgdgasg");
int i = sb.IndexOf('c');
7.分部方法(partial)
对这个不熟,不知道应用场景。以后再补这一块。