五、C# 类

面向对象编程
类是面向对象编程的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参数。
 
总之,分部方法使生成的代码能调用并非一要实现的方法。
 
 
 
 
原文地址:https://www.cnblogs.com/tlxxm/p/4604522.html