c#图解教程_第六章_深入理解类
类成员
类成员如图所示
成员修饰符的顺序
字段和方法的声明可以包括许多如public、private这样的修饰符。排序顺序如下
[特性] [修饰符] 核心声明
- 修饰符
- 修饰符,必须放在核心声明前
- 多个修饰符顺序任意
- 特性
- 特性,必须放在修饰符和核心声明前
- 多个特性顺序任意
例如,public和static都是修饰符,可以用在一起修饰某个声明。因为它们都是修饰符,所以顺序任意。下面两行代码是语义等价的:
public static int MaxVal; static public int MaxVal;
实例类成员
定义:类的每个实例拥有自己的各个类成员的副本,这些成员称为实例成员。
任何一个类的成员被实例后,都具有单独类成员的副本,改变一个实例字段的值,不会影响其他实例中成员的值。
class D { public int Mem1; } class Progarm { static void Main() { D d1=new D(); D d2=new D(); d1.Mem1=10; d2.Mem1=28; Console.WriteLine("d1={0},d2={1}",d1.Mem1,d2.Mem2); } }
静态字段
定义:静态字段被类的所有实例共享,所有实例都访问同一内存位置。
把类的成员设置为静态,那么所有对该类实例化的对象,调用该类成员时都指向同一个类成员
从类的外部访问静态成员
public class A{ public int count; public static int count2
public static void Demo(){} } public class B{ A a = new A(); a.count = 100 //→ .运算符 常规访问 A.count2 =200 //静态访问,静态是属于类的成员,而与实例无关
A.Demo(); //静态访问类成员
}
没有类的实例的静态成员仍然可以被赋值并读取,因为字段与类有关
可以使用静态表示的类成员
成员常量
定义:1.常量声明在类中,称之为成员常量,声明在方法体内称之为本地常量,跳出方法体结束常量,回收常量。 2.常量在声明时必须要赋值(初始化)
特征:
常量不可被声明为static类
1.常量存储是具有特殊区域的,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改,常量本质上在编译时被编译器替换,相当于下面的例子
const A = "123" string a = A → string a = "123" //A在编译时直接被替换成123,而不是去根据A找存储"123"的地址
属性
定义:属性是代表类的实例或类中的一个数据项成员
字段和属性的区别:
- 它是命名的类成员
- 它有类型
- 它可以被赋值和读取
然而和字段不同,属性是一个函数成员
- 它不为数据存储分配内存
- 它是执行代码
属性是指定的一组两个匹配的、称为访问器的方法
- set访问器为属性赋值
- get访问器从属性获取
属性声明和访问器
Set 和 Get 访问器有预定义的语法和语义。
属性和关联字段
一种常见的方式是在类中将字段声明为private以封装字段,并声明一个public属性来控制从类的外部对该字段的访问。
和属性关联的字段常被称为后备字段、后备存储
class C1 { private int TheRealValue=10;//字段:分配内存 public int MyValue //属性:不分配内存 { set{TheRealValue=value;} get{return TheRealValue;} } }
属性和它后备字段的命名有两种约定。
约定一:属性使用Pascal大小写,字段使用Camel大小写。
约定二:属性使用Pascal大小写,字段使用Camel大小写并在开头加"_"号。
//写法一
private int firstField; public int FirstField { get{return firstField;} set{firstField=value;} }
//写法二 private int _secondField; public int SecondField { get{return _secondField;} set{_secondField=value;} }
执行计算
属性访问器不仅对后备字段传进传出数据。也可以执行任何计算。
int demo=100; public int Demo{ set { if(value>100){ demo=value; }else{ demo = 100; } } get { return demo; } }
只读和只写属性
只有get访问器的属性称为只读属性。只读属性是一种安全的,把一项数据从类或类的实例中传出,而不允许太多访问方法
只有set访问器的属性称为只写属性,只写属性是一种安全的,把一项数据从类的外部传入类,而不允许太多访问方法
PS:当编写被第三方调用的程序集时,使用属性限制访问控制,不要用字段
自动实现属性
因为属性经常关联到后备字段,C#提供了自动实现属性,允许只声明属性而不声明后备字段。编译器为你创建隐藏的后备字段,并且字段挂接到get和set访问器上。
class demo { public int MyValue //属性:分配内存 { set;get; } }
静态属性
public class Demo{ public static int MyValue{get;set;} } publci class Program{ static void Main(){ Demo.MyValue = 100; int x = Demo.MyValue; } }
构造函数
带参构造函数和默认构造函数
构造函数允许带参,构造函数允许被重载
当没有对类进行构造函数编写时,那么编译器会提供一个隐式的默认构造函数
public class Demo{ public Demo (){}; //无参构造函数 public Demo (int x){}; //有参构造函数 }
静态构造函数
实例构造函数初始化类的每个新实例,static构造函数初始化类级别的项。通常,静态构造函数初始化类的静态字段。
- 初始化类级别的项
- 在引用任何静态成员之前
- 在创建类的任何实例之前
- 静态构造函数在以下方面和实例构造函数不同
- 静态构造函数声明中使用static
- 类只能有一个静态构造函数,而且不能带参数
- 静态构造函数不能有访问修饰符
- 类既可以有静态构造函数也可以有实例构造函数
- 如同静态方法,静态构造函数不能访问类的实例成员,因此也不能是一个this访问器
- 不能从程序中显式调用静态构造函数,系统会自动调用它们,在:
- 类的任何实例被创建前
- 类的任何静态成员被引用前
class RandomNumberClass { private static Random RandomKey; static RandomNumberClass() { RandomKey=new Random(); } public int GetRandomNumber() { return RandomKey.Next(); } } class Program { static void Main() { var a=new RandomNumberClass(); var b=new RandomNumberClass(); Console.WriteLine("Next Random #:{0}",a.GetRandomNumber()); Console.WriteLine("Next Random #:{0}",b.GetRandomNumber()); } }
对象初始化语句
public class Point { public int X=1; public int Y=2; } class Program { static void Main() { var pt1=new Point(); var pt2=new Point(X=5,Y=6); //实例化时在初始化构造器中赋值 Console.WriteLine("pt1:{0},{1}",pt1.X,pt1.Y); Console.WriteLine("pt2:{0},{1}",pt2.X,pt2.Y); } }
析构函数
析构函数执行在类的实例被销毁前需要的清理或释放非托管资源行为。
非托管资源通过Win32 API获得文件句柄,或非托管内存块。
使用.NET资源无法得到它们,因此如果坚持使用.NET类,就无需为类编写析构函数。
readonly修饰符
readonly初始化时可以先声明不赋值,后期动态赋值也可。
readonly只读修饰符,只允许读取,不允许赋值
readonly double PI=3.1416;
this关键字
定义:this关键字在类中使用,是对当前实例的引用
public class Demo{ public int x; public void SetX(){ this.X = 100; Console.WriteLine(x); } }
索引器
定义:索引器是一组get和set访问器,与属性类似。
索引器和属性
索引器和属性在很多方法类似
- 和属性一样,索引器不用分配内存来存储
- 索引器通常表示多个数据成员
关于索引器的注意事项
- 和属性一样,索引器可以只有一个访问器,也可以两个都有
- 索引器总是实例成员,因此不能声明为static
- 和属性一样,实现get、set访问器的代码不必一定关联到某字段或属性。这段代码可以什么都不做,只要get访问器返回某个指定类型值即可
声明索引器
- 索引器没有名称。在名称的位置,关键词是this
- 参数列表在方括号中
- 参数列表中至少声明一个参数
//语法
T this[T index,int index2....] //根据传入的值判断用哪个索引器,可以允许有多个索引参数 { // get 访问器 get { // 返回 index 指定的值 } // set 访问器 set { // 设置 index 指定的值 } }
using System; namespace IndexerApplication { class testIndexer{ static public int size = 5; private string[] namelist = new string[size]; public testIndexer() { for(int i = 0; i < size; i++) { namelist[i] = "null"; } } public string this[int index] { get { string tmp; if (index >= 0 && index < size) { tmp = namelist[index]; } else { tmp = "null"; } return tmp; } set { if (index >= 0 && index < size) { namelist[index] = value; } } } public int this[string name] { get { int index = 0; while (index >= 0) { if (namelist[index] == name) { return index; } index++; } return index; } } static void Main(string[] args) { testIndexer names = new testIndexer(); names[0] = "A"; names[1] = "B"; names[2] = "C"; //输出所有元素 for(int i = 0; i < testIndexer.size; i++) { Console.WriteLine(names[i]); } Console.Write("输出names中内容为A的元素索引:"); Console.WriteLine(names["A"]); Console.ReadKey(); } } }
访问器的访问修饰符
本章中,你已看到了两种带get、set访问器的函数成员:属性和索引器。默认情况下,成员的两个访问器的访问级别和成员自身相同。也就是说,如果一个属性有public访问级别,那么它的两个访问器也是public的。
不过,你可以为两个访问器分配不同访问级别。例如,下面代码演示了一个常见且重要的例子–set访问器声明为private,get访问器声明为public。(get之所以是public,是因为属性的访问级别就是public)
注意:在这段代码中,尽管可以从类的外部读取该属性,但却只能在类的内部设置它。这是非常重要的封装工具。
class Person { public string Name{get;private set;} public Person(string name) { Name=name; } } class Program { static public void Main() { var p=new Person("Capt,Ernest Evans"); Console.WriteLine("Person's name is {0}",p.Name); } }
访问器的访问修饰符有几个限制。最重要的限制如下。
- 仅当成员(属性或索引器)既有get访问器也有set访问器时,其访问器才能有访问修饰符
- 虽然两个访问器都必须出现,但它们中只能有一个有访问修饰符
- 访问器的访问修饰符必须比成员的访问级别有更严格的限制性,即访问器的访问级别必须比成员的访问级别低,详见下图
例如,如果一个属性的访问级别是public,在图里较低的4个级别中,它的访问器可以使用任意一个。但如果属性的访问级别是protected,则其访问器唯一能使用的访问修饰符是private。
分部类和分部类型
类的声明可以分割成几个分部类的声明
- 每个分部类的声明都含有一些类成员的声明
- 类的分部类声明可以在同一文件中也可以在不同文件中
每个局部声明必须标为partial class,而不是class。分部类声明看起来和普通类声明相同。
类型修饰符partial不是关键字,所以在其他上下文中,可以把它用作标识符。但直接用在关键字class、struct或interface前时,它表示分部类型。
分部类
分部方法
分部方法是声明在分部类中不同部分的方法。
分部方法的两个部分如下
- 定义分部方法声明
- 给出签名和返回类型
- 声明的实现部分只是一个分号
- 实现分部方法声明
- 给出签名和返回类型
- 是以正常形式的语句块实现
关于分部方法需要了解的重要内容如下
- 定义声明和实现声明的签名和返回类型必须匹配。签名和返回类型有如下特征
- 返回类型必须是void
- 签名不能包括访问修饰符,这使分部方法是隐式私有的
- 参数列表不能包含out参数
- 在定义声明和实现声明中都必须包含上下文关键字partial,直接放在关键字void前
- 可以有定义部分而没有实现部分。这种情况下,编译器把方法的声明以及方法内部任何对方法的调用都移除。不能只有实现部分而没有定义部分。
下面是一个名为PrintSum的分部方法的示例
- 因为分部方法是隐式私有的,PrintSum不能从类的外部调用。方法Add是调用PrintSum的公有方法
-
partial class MyClass { 必须是void ↓ partial void PrintSum(int x,int y);//定义分部方法 public void Add(int x,int y) { PrintSum(x,y); } } partial class MyClass { partial void PrintSum(int x,int y)//实现分部方法 { Console.WriteLine("Sum i {0}",x+y); } } class Program { static void Main() { var mc=new MyClass(); mc.Add(5,6); } }