1.1实力构造器和类(引用类型)
构造器:是允许将类型的实例初始化为良好状态的一种特殊方法。
在创建一个引用类型的实例时:
1.为实例的数据字段分配内存。
2.初始化对象的附加字段(类型对象指针和同步索引块)。
3.调用类型的实例构造器来设置对象的初始状态。
C#语法提供了一个简单的语法,允许在构造引用类型的一个实例时,对类型中定义的字段进行初始化(内联方式初始化)
internal sealed class SomeType{ private Int32 m_x=5; private String m_s="Hi Here"; private Double m_d=3.1415926; private Byte m_b; //构造器 public SomeType(){} punlic SomeType(Int32 x){} public SomeType(String s){ m_d=10; } }
比如获取一个String 参数的构造器,编译器生成的代码首先初始化m_x,m_s,m_d,再调用基类(Object)的构造器,再执行自己的代码(最后是用10覆盖m_d原先的值)
C#利用this关键字显示调用另一个构造器
internal sealed class SomeType{ private Int32 m_x; private String m_s; private Double m_d; private Byte m_b; //该构造器将所有字段设为默认值 //其他所有构造器都显示调用这个构造器 public SomeType() { m_x=5; m_s="Hi there"; m_d=3.1415; m_b=0xff; } //该构造器将所有字段设为默认值,然后修改m_x值为x public SomeType(Int32 x):this(){ m_x=x; } //该构造器将所有字段设为默认值,然后修改m_x值为x public SomeType(String s):this(){ m_s=s; } }
1.2实例构造器和结构(值类型)
C#编译器不会为值类型生成默认的无参构造器,但CLR允许为值类型定义构造器(但必须是有参的构造器,结构不能包含显示的无参构造器),但必须是显示的调用它们
internal struct SomeValType{ //不能在值类型中内联实例字段的初始化 private Int32 m_x=5; } 另:在访问值类型的任何一个字段之前,都需要对全部字段进行赋值 internal struct SomeValType{ private Int32 m_x,m_y; //C#允许为值类型定义有参构造器 public SomeValType(Int32 x){ m_x=x; //m_y没有在这里初始化 } } //对值类型的全部字段进行赋值的一个替代方案 public SomeValType(Int32 x){ //看起来很奇怪,但编译没有问题,会将所有字段初始化为0/null this=new SomeValType(); m_x=x; //m_y已初始化为0 }
在值类型的构造器中,this代表值类型本身的一个实例,用new 创建的值类型的一个实例可以赋给this,在new过程中,会将所有字段置为零
注:在引用类型的构造器中,this是只读的,不能对它进行赋值。
1.3类型构造器
也称静态构造器、类构造器、类型初始化器
设置类型的初始状态,没有默认的类型构造器,如果定义只能定义一个,类型构造器没有参数,必须标记为static,总是私有的(C#默认会把它标记为private),一个类型构造器只执行一次
类型构造器的代码只能访问静态字段,常用来初始化这些字段
注意:由于CLR保证一个类型构造器在每个AppDomain中只执行一次,而且是线程安全的,所以非常适合在类型构造器中初始化类型需要的任何单实例对象。
1.4操作符重载
CLR对操作符重载是一无所知,它甚至不知道什么是操作符,是编程语言定义了操作符的含义,以及这个操作符出现时应该生成什么样的代码。
C#语言,向基元类型的数字应用+符号,编译器会生成将两个数字加到一起的代码。应用于字符串会生成将两个字符串连接到一起的代码。
尽管CLR对操作符一无所知,但它确实规定了语言应如何公开操作符重载,以便由另一种语言的代码使用。
CLR规范要求操作符重载方法必须是public和static方法
C#编程语言要求操作符重载方法至少有一个参数的类型与当前定义这个方法的类型相同。
public sealed class Complex{
public static Complex operator+(Complex c1,Complex c2){....}
}
1.5转换操作符方法
public sealed class Rational{ //由一个Int32构造一个Rational public Rational(Int32 num){} //由一个Single构造一个Rational public Rational(Single num){} //将一个Rational转换成一个Int32 public Int32 ToInt32(){} //将一个Rational转换成一个Single public Single ToSingle(){} } 然而可以用转换操作符重载,CLR规范重载方法必须public 和static ,参数类型和返回类型二者必有其一与定义转换方法的类型相同 public sealed class Rational{ //由一个Int32隐式构造并返回一个Rational public static implicit operator Rational(Int32 num){ return new Rational(num); } //由一个Rational显示返回一个Int32 public static explicit operator Int32(Rational r){ return r.ToInt32(); } }
1.6扩展方法
这个东西在项目开发中是经常用到的
规则和原则:
1.C#支持扩展方法,不支持扩展属性、扩展事件、扩展操作等。
2.扩展方法(第一个参数前面有this的方法)必须在非泛型静态类中声明,然而,类名没有限制,可以随便叫什么名字,当然扩展方法至少有一个参数,而且只有第一个参数能用this 关键字来标记。
3.C#编译器查找静态类中定义的扩展方法时,要求这些静态类本身必须具有文件作用域(类要具有整个文件的作用域,而不能嵌套到一个类中,只具有该类的作用域)
4.静态类可以取任何名字 5.多个静态类可以定义相同的扩展方法,如果编译器检测到2个或多个扩展方法,就会报错,所以在调用的时候就不能用实例方法语法来调用这个静态方法,相反,必须使用静态方法语法,换言之,必须显示指定静态类的名称,明确告诉编译器要调用哪个方法。
6.扩展方法第一个参数不应该是System.Object类型
7.扩展方法具有潜在的版本控制问题,如果MS未来会对他们的String添加与我们对其扩展的同名方法,那时我们的扩展方法就会失效了
用扩展方法扩展各种类型
接口类型定义扩展方法
public static void ShowItem<T>(this IEnumerable<T> collection) { foreach (var item in collection) { Console.WriteLine(item); } }
扩展方法是Linq技术的基础
还可以为委托定义扩展方法
Action a = "Jeff".ShowItem;
a();
结果:
J
e
f
f
下面举例2个常用的字符串转全角、半角的扩展方法
/// <summary> /// 转全角 /// </summary> /// <param name="input">任意字符串</param> /// <returns>全角字符串</returns> public static String ToSBC(this String input) { Char[] c = input.ToCharArray(); for (int i = 0; i < c.Length; i++) { if (c[i] == 32) { c[i] = (Char)12288; continue; } if (c[i] < 127) { c[i] = (Char)(c[i] + 65248); } } return new String(c); } /// <summary> /// 转半角 /// </summary> /// <param name="input">任意字符串</param> /// <returns>半角字符串</returns> public static String ToDBC(this String input) { Char[] c = input.ToCharArray(); for (int i = 0; i < c.Length; i++) { if (c[i] == 12288) { c[i] = (Char)32; continue; } if (c[i] > 65280 && c[i] < 65375) { c[i] = (Char)(c[i] - 65248); } } return new String(c); }
1.7分部方法
分部方法的定义和分部类型类似,只需在方法定义前添加partial关键字,但分部方法只能拆分成两个部分——一部分是定义声明(Definition Declaration),另一部分是实现声明(Implement Declaration)
分部方法的语法非常简单,但有一些事项要注意:
1.如果没有实现分部方法,编译器不会生成任何代表分部方法的元数据。不会生成任何调用分部方法的元数据
2.它们只能在分部类或结构中声明
3.分部方法的返回值始终是void,任何参数不能用out来标记,是因为方法必须初始化它,而这个方法可能不存在,分部方法可以有ref参数,可以是泛型方法,可以是实例或静态方法,也可以标记unsafe
4.禁止在分部方法声明添加private关键字,因为它总被视为private
5.分部方法不可以是虚拟(virtual)的
6.分部方法不可以是外部(extern)的
7.分部方法可以是泛型方法,泛型约束必须放置在定义声明中,但也可以在事先声明中重复说明。在定义声明和实现声明中,类型参数和类型参数的名字不一定必须一致
8.不能将分部方法封装到一个委托中
分部方法的应用场景:
分部方法和分部类型的初衷是类似的,一方面可以使得不同的开发者能够同时编写一个类型的不同部分,另一方面可以分离自动生成的代码和用户手写的代码。和分部类型一样,分部方法也会在编译初期被合并成一个方法定义。猜测:从微软的角度来看,第二个“初衷”可能才是真正的初衷
场景1 轻量级事件处理:
有的时候,自动生成的代码需要事件这类语言构造来通知用户对某些操作进行处理,但实际上用于编写的代码就位于自动生成的类型之中。此时,或者需要触发一个事件,或者就需要生成一个virtual方法来让用户继承。但无论是事件还是继承,开销都是比较大的,所以可以通过分部方法来实现轻量级的处理方式
using System; namespace ConsoleApplication1 { partial class Customer { private String name; //分部方法声明 partial void OnBeforeUpdateName(); partial void OnAfterUpdateName(); partial void OnUpdateName(); public String Name { get { return name; } set { OnBeforeUpdateName(); OnUpdateName(); name = value; OnAfterUpdateName(); } } } } using System; namespace ConsoleApplication1 { partial class Customer { //分部方法的实现 partial void OnBeforeUpdateName() { Console.WriteLine("OnBeforeUpdateName"); } partial void OnAfterUpdateName() { Console.WriteLine("OnAfterUpdateName"); } partial void OnUpdateName() { Console.WriteLine("OnUpdateName"); } } }
这里定义了三个分部方法,其意义不言而喻。假设这是系统自动生成的代码,则我们只需在另外一个源代码文件中的partial class Customer中实现这几个分部方法即可。
场景2 自定义DataContext中的Insert、Update、Delete方法
当使用Linq to SQL向项目中加入了实体类之后,还会创建一个XxxDataContext类,这个类继承自DataContext类,并且是partial的。这个类封装了具体的数据库操作功能(实体类仅封装数据库中的数据),如对象的插入、更新和删除等。
下面我们来看一下这个自动生成的类定义:
[System.Data.Linq.Mapping.DatabaseAttribute(Name="AdventureWorks")] public partial class AdventureWorksDataContext : System.Data.Linq.DataContext { private static System.Data.Linq.Mapping.MappingSource mappingSource = new AttributeMappingSource(); #region Extensibility Method Definitions partial void OnCreated(); partial void InsertAWBuildVersion(AWBuildVersion instance); partial void UpdateAWBuildVersion(AWBuildVersion instance); partial void DeleteAWBuildVersion(AWBuildVersion instance); ......
这里我们可以看到一系列的partial方法。其中第一个OnCreated实际上属于场景1中描述的情况,是一个轻量级的事件,表示DataContext环境对象创建完毕。而其他partial方法则用于自定义DataContext的IUD操作。对于每一个表(实体类),这里都会出现一组InsertXxx、UpdateXxx和DeleteXxx方法。如果我们希望自定义删除行为(如希望将一个IsDelete字段设置为true来表示已删除),则可以在另一个文件中扩展这个partial类,并为对应的Delete方法提供实现声明。
场景3 新的调试信息输出方法
臆想的场景,在分部方法的协助下,我们可以写出这样的代码:
partial class CA { partial void DebugPrint(string msg); void F() { //other code DebugPrint("aaa"); } } partial class CA { #if DEBUG partial void DebugPrint(string msg); { Debug.WriteLine(msg); } #endif }
这样做的好处在于,我们还是反过来说罢,如果不这样做,必须在每次调用调试代码时都加入#if判断。而这样可以将调试代码都写成方法,在一处用#if进行判断。缺点在于,由于分部方法必须是私有的,所以必须针对每个类写一套调试代码。