面向对象编程
类是面向对象编程的3个主要特征---封装、继承和多态性---的基础。
封装允许隐藏细节。
继承
继承关系至少涉及两个类,其中一个类(基类)是另一个类的更泛化的版本。
为了从一个类型派生或者说从它继承,需要对那个基类型进行特化,这意味着要对基类型进行自定义,针对特定的目的调整它。
继承最关键的一点在于,所有派生类型都继承了基类型的成员。在派生类型中,
基成员的实现通常可以改动,但是不管是否修改基成员,派生类型中除了:有派生类型包含的那些
成员外,还可以包含基类型的成员。
多态性意味着一个方法或类型可以具有多种形式的实现。
对象和类
类相当于一个模块,对象是这个类的一个实例,相当于这个用这个模块创造一个产品。
使用类创建一个新对象的过程称为实例化。
实例化一个对象,需要使用new运算符指示“运行时”为一个对象分配内存,实例化对象,
并返回对实例的一个引用(整型数组)。
在堆中申请的内存(如new 申请分配),由垃圾回收器自动回收。(运行时,会在最后一次
访问对象之后,并在应用程序结束之前的某个时候,自动地回收内在。)
实例字段(成员变量)
可以在声明时设置字段的初始值。
实例方法(成员方法)
可以访问对象上的字段。
this关键字
在类的实例成员内部可以获取当前对象的一个引用。
this在概念上是传给每个实例方法的一个隐式参数,它返回对象本身的一个实例。
访问修饰符
在面向对象编程中,封闭不仅仅是将数据和行为组合到一起,它同时还意味着隐藏一个类中的数据,
使一个类的内部工作机制是最小程度地对类的外部公共,这减少了调用者对数据进行不恰当修改的几率。
访问修饰符是用途是提供封装。
标识了所修饰成员的封装级别。
public、private、protected、internal和protected internal 总共5个。
注:如果不为类成员添加访问修饰符,那么默认使用的是private。
属性
为了实现某些字段从外部只读等的效果。一般采取的办法是将字段标记为私有,然后提供取值
和赋值方法来访问和修改数据,只读时,只提供取值方法,不提供赋值方法。
C#提供了显式的语法支持:属性。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 string firstName = "aaaaa", middleName = "bbbbbbbbb", lastName = "cccccccc"; 6 string fullName; 7 Employee e = new Employee(); 8 e.FirstName = firstName; 9 e.LastName = lastName; 10 Console.WriteLine(e.FirstName + "." + e.LastName); 11 } 12 } 13 public class Employee 14 { 15 private string _FirstName; 16 public string FirstName 17 { 18 set 19 { 20 _FirstName = value; 21 } 22 get 23 { 24 return _FirstName; 25 } 26 } 27 private string _LastName; 28 public string LastName 29 { 30 set 31 { 32 _LastName = value; 33 } 34 get 35 { 36 return _LastName; 37 } 38 } 39 40 }
属性的关键点在于,它提供了从编程角度看类似于字段的API,但是事实上,并不存在这样的字段。
在大括号中,要添加具体的属性实现代码。两个可选的部分构成了一个属性的实现。
set get
自动实现的属性 C#3.0
声明一个属性时,不实际地实现任何取值或同仁方法,也不声明任何支持字段
1 public class Employee 2 { 3 4 public string Title { set; get; } 5 public Employee Manager { set; get; } 6 7 }
使用属性的好处在于,如果需要添加一个额外代码:比如在set方法中添加验证,数据的合法性。
虽然属性的声明发生了变化,但是调用属性的代码不进行任何更改。
建议使用属性,来调用字段,不建议直接调用字段。
实现只读和只写属性:
通过移除某个属性的取值方法或赋值方法部分,可以改变属性的可访问性。
为取值方法和赋值方法指定访问修饰符
1 public class Employee 2 { 3 private string _FirstName; 4 public string FirstName 5 { 6 private set 7 { 8 _FirstName = value; 9 } 10 get 11 { 12 return _FirstName; 13 } 14 } 15 private string _LastName; 16 public string LastName 17 { 18 private set 19 { 20 _LastName = value; 21 } 22 get 23 { 24 return _LastName; 25 } 26 } 27 public string Title { private set; get; } 28 public Employee Manager { private set; get; } 29 30 }
注:这个访问修饰限制性必须比应用于整个属性的访问修饰符更为严格。
属性的赋值和取值,可以是任何操作,get 必须返回一个数据类型一致的数据。
属性和方法调用不允许作为ref或out参数值使用
原因:ref和out需要将内存地址传给目标方法,但是,由于属性可能是没有支持字段的虚字段,
也有可能是只读/只写的,因此不可能传递其基础存储的地址,不过可以通过中间变量来实现。
如:
1 public string Title 2 { 3 private set; 4 get 5 { 6 return _FirstName + "." + _LastName; 7 } 8 }
构造器(构造函数)
为了定义构造器,要创建一个返回类型的方法,而且方法名必须完全和类名相同。
构造器是用来创建对象实例的方法。
new运算符返回的是被实例佛手对象的类型。
注:如果一个字段在声明时赋了初始值,且在构造函数中也赋予了初始值,那么最终生效的是构造器
内部的赋值,它会覆盖声明时的任何赋值。
所以应该避免在两个地方同时赋值。
C#编译器会自动添加一个默认构造器(无参数),且如果自定义了构造函数,不再自动添加默认构造函数。
C#3.0对象初始化器
在调用的时候使用
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 string firstName = "aaaaa", middleName = "bbbbbbbbb", lastName = "cccccccc"; 6 string fullName; 7 Employee e = new Employee(firstName, lastName) { Title = "MyTitle", Content="Content" }; 8 9 Console.WriteLine(e.Title + ":" + e.Content + "" + ":" + e.FirstName + "." + e.LastName); 10 Console.ReadLine(); 11 12 13 } 14 15 16 } 17 public class Employee 18 { 19 public Employee(string firstName, string lastName) 20 { 21 22 FirstName = firstName; 23 LastName = lastName; 24 } 25 private string _FirstName; 26 public string FirstName 27 { 28 private set 29 { 30 _FirstName = value; 31 } 32 get 33 { 34 return _FirstName; 35 } 36 } 37 private string _LastName; 38 public string LastName 39 { 40 private set 41 { 42 _LastName = value; 43 } 44 get 45 { 46 return _LastName; 47 } 48 } 49 public string Title 50 { 51 set; 52 get; 53 54 } 55 public string Content 56 { 57 set; 58 get; 59 60 } 61 public Employee Manager { set; get; } 62 63 }
终结器
终结器是在一个对象最后一次活动之后,并在程序终止之前执行,具体地说,垃圾回收器会在
一次垃圾回收过程中识别出带有终结器的对象。之后,它并不是立即回收这些对象,而是将它们添加
到一个终结列中。一个独立的线程遍历终结队列中的每一个对象,调用它们的终结器,然后把它们从队列
中删除,使它们再次可供垃圾回收器调用。
注:构造器可重载
使用this调用另一个构造器
这称为构造器链,它是用构造器初始化器来实现的。构造器初始化器会在执行当前构造器的实现之前
判断要调用另外哪一具构造器,并进行调用。
1 public Employee(string firstName, string lastName) 2 { 3 4 FirstName = firstName; 5 LastName = lastName; 6 } 7 public Employee(string Title, string firstName, string lastName) 8 : this(firstName, lastName) 9 { 10 this.Title = Title; 11 }
注:可写一个函数专门用来初始化,然后在构造器中调用。
匿名类型
在C#3.0中引入了对匿名类型的支持。它们是由器动态生成的数据,而不是通过显式的类定义来声明的。
var
编译器遇到匿名类型的语法时,就会自动生成一个CIL类,该类具有与匿名类型声明中的命名值
和数据类型对应的属性。
静态成员
使用static修饰
public static int Count = 0;
与实例字段不同,如果不对静态字段进行初始化,静态字段将自动获得相应数据类型的默认值。
另外,一个静态字段即使没有显式地赋值,也可以被访问。
在类的外部访问静态字段时,需要使用类名。
注:上下文就是作用域
静态方法
和静态字段一样,要直接在类名之后访问静态方法。
由于静态方法不是通过一个特定的实例来引用,所以this关键字在静态方法中是无效。
所以也不能直接在静态中访问类中声明的实例成员,所有实例成员必须通过一个对象引用才能访问。
静态构造器
除了静态字段和方法,C#还支持静态构造器。静态构造器用来对类进行初始化。静态构造器不是显式调用的
,运行时会在首次访问类时自动调用静态构造器。
所谓“首次访问类”,可能是调用一个普通构造器,也可能是访问类的一个静态方法或者字段。
在构造器中的赋值会覆盖声明时的初始化值。
静态属性
与实例属性一样,只是是属于类的。
使用静态属性几乎肯定要比使用公共静态字段好,因为公共静态字段在任何地方都能调用,而静态
属性则至少提供了一定程度的封装。
静态类
使用static修饰
这个类不包含任何实例成员,所以创建一个实例化的类是没有意义的。
静态类的另一个特殊在于,C#编译器会自动在CIL代码中把它标记为abstract和sealed。
这会将类指定为不可扩展,换言之,不能从它派生出其他类。
扩展方法(必须在静态类中定义)
C#3.0
引入了扩展方法(extension method)的概念。它能为一个不同的类模拟出一个实例方法。
使静态方法的第一个参数成为要扩展的类型,并在类型名称前面附加this关键字。
1 public static class DirectoryInfoExtension 2 { 3 public static string Message(this Employee e, string message1, string message2) 4 { 5 return message1 + message2; 6 } 7 } 8 string firstName = "aaaaa", middleName = "bbbbbbbbb", lastName = "cccccccc"; 9 string fullName; 10 Employee e = new Employee(); 11 12 Console.WriteLine(e.Title + ":" + e.Content + "" + ":" + e.FirstName + "." + e.LastName); 13 14 Console.WriteLine(e.Message(firstName,lastName)); 15 Console.ReadLine();
C#通过这一处小小的改进,就使我们能为任何类添加实例方法,即使是那些不在同一个程序集中的类。然后,根据最终生成的CIL代码,你会发现扩展方法和一个普通的静态方法的代码是完全一样的。
扩展方法的要求如下:
1、第一个参数是要扩展或者操作的类型,这称为"被扩展的类型"
2、为了指定扩展方法,要在扩展的类型名称前面附加this修饰符
3、要将方法作为一个扩展方法来访问,要用using指令导入扩展类型的命名空间,或者
使扩展类和调用代码在同一个命名空间中。
注:如果扩展方法的签名已经和扩展类型中的一个签名匹配,扩展方法永远不会得到调用,除非是作为
一个普通的静态方法。
应该尽量少用扩展方法,而使用继承来扩展。
封装数据
两个特殊的字段修饰符,const、readonly
1、const
和const局部变量一样,const字段(常量字段)包含的是在编译时确定的一个值,它不可以在运行时改变。
常量字段自动成员静态字段,因为不需要为每个对象实例都生成一个新的字段实例。
但是,假如将一个常量字段显式地声明为static,会造成一个编译错误。
public 常量应该是恒定值,否则,如果对它进行了修改,那么在使用它的程序集中,不一定能反映出
这个修饰。需要重新编译。
将来可能改变的值应该指定为readonly,不要指定为常量。
2、readonly
和const不同,readonly修饰符只能用于字段(不能用于局部变量),它指出字段值只能从构造器
中更改,或者直接在声明时指定。
每个实例的readonly字段都可以是不同的,除此之外,readonly字段即可以是实例字段,也可以是静态
字段。另一个关键区别在于,可以在执行时为readonly字段赋值,而非只能在编译时赋值。
嵌套类
在类中除了定义方法和字段,还可以定义另一个类。这样的类称为嵌套类
嵌套类中的this成员指的是嵌套类的一个实例,而不是包容类。
嵌套类的另一个有趣的特点是它能够访问包容类的任何成员,其中包括私有成员。但反之则不然,包容类不能访问嵌套类的私有成员。
分部类
C#2.0新增的另一个语言特性是分部类(partial class)。
使用partial修饰
分部类是一个类的多个部分,这些部分可以合并成一个完整的类。
使用上下文关键字partial来声明一个分部类。
可以放在不同的文件当中。
分部类不允许对编译好的类(其他程序集中的类)进行扩展。只能利用分部类在同一个程序集
中将一个类的实现分解到多个文件中。
分部方法
C#3.0
使用partial修饰
分部方法只能存在于分部类中。另外和分部类相似,其主旨是为代码的生成提供方便。
分部方法允许声明一个方法而不需要一个实现。
然后,如果包含了可选的实现,这个实现就可以放到某个姊妹分部类定义中。
也就是声明和定义分别放在分部类的不同地方。
注:分部方法必须返回void,如果不是返回null,同时没有提供实现,那么调用一个未实现的方法。
返回什么才合理呢。所以只允许方法返回void,out参数在分部方法中是不允许的。如果
需要一个返回值,可以使用ref参数。
总之,分部方法使生成的代码能调用并非一要实现的方法。