一、类
1、类的定义和类成员
程序的数据和功能被组织为逻辑上相关的数据项和函数的封装集合,并称为类。
类是一个能存储数据并执行代码的数据结构。它包含数据成员和函数成员。
表-类成员的类型
数据成员存储数据 | 函数成员执行代码 |
---|---|
字段、常量 | 方法、属性、构造函数、析构函数 运算符、索引器、事件 |
//显式和隐式字段初始化
class MyClass
{
int F1; //初始化为 0 - 值类型
string F2; //初始化为 null - 引用类型
int F3 = 25; //初始化为 25
string F4 = "abcd"; //初始化为 “abcd”
}
2、为数据分配内存
类的实例是引用类型,所以需要使用 new 运算符实例化:
- new 运算符为任意指定类型的实例分配并初始化内存。它依据类型的不同从栈或堆里分配。
- 如果将内存分配给一个引用类型,则对象创建表达式返回一个引用,指向在堆中被分配并初始化的对象实例。
3、属性
1)只读和只写属性
- 只有 get 访问器的属性称为只读属性。只读属性能够安全地将一个数据项从类或类的实例中传出,而不必让调用者修改属性值。
- 只有 set 访问器的属性称为只写属性。只写属性很少见,因为它们几乎没有实际用途。
- 两个访问器中至少有一个必须定义,否则编译会产生一条错误消息。
2)属性与公有字段的区别
- 属性是函数成员而不是数据成员,允许你处理输入和输出,而公有字段不行。
- 属性可以只读或只写,而字段不行。
- 编译后的变量和编译后的属性语义不同。
《C# 图解教程-7.10.7 属性与公有字段》书上附加内容:
如果要发布一个由其他代码引用的程序集,那么第三点将会带来一些影响。例如,有的时候开发人员可能想用共有字段代替属性,因为如果以后需要为字段的数据增加处理逻辑的话,可以再把字段改为属性。这没错,但是如果那样修改的话,所有访问这个字段的其他程序集都需要重新编译,因为字段和属性在编译后的语义不一样。另外,如果实现的是属性,那么只需要修改属性的实现,而无须重新编译访问它的其他程序集。
我需要提供一些代码例子来加强理解:
如果程序员对某一情况最初始的想法是只使用字段:
//程序集Stuclass Student{public int Id;}被其他程序集ABC...访问引用
class Other
{
public Other()
{
Student stu = new Student();
stu.Id = 2;//此处是字段
}
}
但是产品经理说,下版本有这样的需求:Student 的 Id 不能有负值。哦豁,如果按照以上的代码来设计,那估计其他访问Stu的所有程序集都必须做判断的逻辑处理。如果只是把Id字段改为属性类型,同样也得需要重新编译其他所有访问 Stu 的程序集才能生效,因为它们访问的类型都必须保持一致。否则,会报错。
所以,最开始就应该这么做:
//程序集Stu 版本1class Student{private int id;public int Id{get { return id; }set{id = vvalue;}}}//程序集Stu 版本2
class Student{private int id;public int Id{get { return id; }set{if (id > 0)id = value;elseid = 0;}}}其他所有程序集访问Stu
class Other
{
public Other()
{
Student stu = new Student();
stu.Id = 2;//此处是属性,对版本1和版本和2都能访问
}
}
3)自动实现属性(简称自动属性)
- 不声明后备字段——编译器根据属性的类型分配存储。
- 不能提供访问器的方法体——它们必须被简单地声明为分号,get 担当简单的内存读,set 担当简单的写。但是因为无法访问自动属性的方法体,所以在使用自动属性时调试代码通常会更更加困难。
4)静态属性
静态属性的访问器和所有静态成员一样,具有以下特点:
- 不能访问类的实例成员,但能被实例成员访问。
- 不管类是否有实例,它们都是存在的。
- 在类的内部,可以仅使用名称来引用静态属性。
- 在类的外部,可以通过类名或者使用 using static 结构来引用静态属性。
4、其他静态成员类型
可以声明为 static 的类成员类型做了勾选标记。
表-可以声明为静态的类成员类型
数据成员(存储数据) | 函数成员(执行代码) |
---|---|
✅字段 | ✅方法 |
✅类型 | ✅属性 |
🚫常量 | ✅构造函数 |
✅运算符 | |
🚫索引器 | |
✅事件 |
5、静态构造函数
静态构造函数初始化类的静态字段
静态构造函数与实例构造函数的区别:
- 1)静态构造函数声明中使用 static 关键字
- 2)类只能有一个静态构造函数,而且不能带参数。
- 3)静态构造函数不能有访问修饰符。
6、屏蔽基类的成员(隐藏)
- 屏蔽基类的成员(数据成员,静态成员,函数成员),可使用 new 修饰符。
- 函数成员,签名由名称和参数列表组成,不包括返回类型。
7、抽象成员
虚成员 | 抽象成员 | |
---|---|---|
关键字 | virtual | abstract |
实现体 | 有实现体 | 没有实现体,被分号取代 |
在派生类中被覆写 | 能被覆写,使用 override | 必须被覆写,使用 override |
成员的类型 | 方法属性事件索引器 | 方法属性事件索引器 |
二、弱引用
实例化一个类或结构时,只要有代码引用它,就会形成强引用。
根据例子来理解强引用:
MyClass myClassVariable = new MyClass();
myClassVariable 引用该类的对象,那么只要 myClassVariable 在作用域内,就存在对 MyClass 对象的强引用。
使用弱引用的情况:
强引用意味着垃圾回收器不会清理 MyClass 对象使用的内存。如果 MyClass 对象很大,并且不经常访问,就可以创建对象的弱引用。
弱引用允许创建和使用对象,但是垃圾回收器运行时,就会回收对象并释放内存。
弱引用是使用了系统提供的 WeakReference 类创建的。
使用弱引用的例子:
class MathTest{public int Value;public int GetSquare(){return Value * Value;}public static int GetSquareOf(int x){return x * x;}public static double GetPi(){return 3.14159;}}class Program{static void Main(string[] args){WeakReference mathReference = new WeakReference(new MathTest());MathTest math;//检查确保MathTest对象未被回收//若IsAlive为true,就从目标属性得到MathTest对象的引用if (mathReference.IsAlive){math = mathReference.Target as MathTest;math.Value = 30;Console.WriteLine("Value field of math variable contains " + math.Value);Console.WriteLine("Square of 30 is " + math.GetSquare());}else{Console.WriteLine("Reference is not available.");}//调用垃圾回收器GC.Collect();//再次尝试获取MathTest对象if (mathReference.IsAlive){math = mathReference.Target as MathTest;}else{//此处说明MathTest对象已被回收。//如果想要再使用MathTest对象,就必须实例化一个新的MathTest对象。Console.WriteLine("Reference is not available.");}Console.ReadKey();}}
三、匿名类
匿名类型只是一个继承自 Object 且没有名称的类。
var captain = new { FirstName = "Leonard", MiddleName = "", LastName = "McCoy" };
如果通过另一个对象来设置初始值:
var person = new { FirstName = "Leonard", MiddleName = "", LastName = "McCoy" };var captain = new { person.FirstName, person.MiddleName, person.LastName };
四、部分类
partial 关键字允许把类、结构、方法或接口放在多个文件中。
分部方法分为两部分:
- 定义分部方法
- 实现分部方法
分部方法定义声明和实现声明的签名和返回类型必须匹配。签名和返回类型有如下特征:
- ==返回类型必须是 void ==。
- 签名不能包括访问修饰符,这使分部方法是隐式私有的。
- 在定义声明和实现声明中都必须包含上下文关键字 partial,并且直接放在关键字 void 之前。
//文件A:partial class MyClass{partial void PrintSum(int x, int y);public void Add(int x, int y){PrintSum(x, y);}}//文件B:partial class MyClass{partial void PrintSum(int x, int y){Console.WriteLine("Sum is {0}", x + y);}}
五、静态类
如果类只包含静态的方法和属性,该类就是静态的。
static class StaticUtilities{public static void HelperMethod(){}}class Program{static void Main(string[] args){StaticUtilities.HelperMethod();Console.ReadKey();}}
六、Object 类
所有的 .NET 类都派生自 System.Object。如果在自定义类时没有指定基类,编译器就会自动假定整个类派生自 Object。
System.Object() 方法
我在这里把书上的部分内容,进行排版,并做成一个表单的形式来显示该部分内容:
方法 | 描述 |
---|---|
ToString() | 是获取对象的字符串表示的一种便捷方式。⚪当只需要快速获取对象的内容,以进行调试时,就可以使用这个方法。⚪在数据的格式化方面,它几乎没有提供选择:例如,在原则上日期可以表示为许多不同的格式,但 DateTime.ToString() 没有在这方面提供任何选择。如果需要更复杂的字符串表示。例如,考虑用户的格式化首选项或区域性(区域),就应实现 IFormattable 接口。 |
GetHashCode() | 如果对象放在名为映射(也称为散列表或字典)的数据结构中,就可以使用整个方法。处理这些结构的类使用方法确定把对象放在结构的什么地方。如果希望把类用作字典的一个键,就需要重写 GetHashCode() 方法。实现该方法重载的方式有一些相当严格的限制。 |
Equals()(两个版本) 和 ReferenceEquals() | 主要有3个用于比较对象相等性的不同方法,这说明 .NET Framework 在比较相等性方面有相当复杂的模式。这3个方法和比较运算符 “ == ” 在使用方式上有微妙的区别。而且,在重写带一个参数的虚 Equals() 方法时也有一些限制,因为 System。Collections 名称空间中的一些基类要调用该方法,并希望它以特定的方式执行。 |
Finalize() | 它最接近 C++ 风格的析构函数,在引用对象作为垃圾被回收以清理资源时调用它。 Object 中实现的 Finalize() 方法实际上什么也没有做,因而被垃圾回收器忽略。如果对象拥有对未托管资源的引用,则在该对象删除时,就需要删除这些引用,此时一般要重写 Finalize()。垃圾回集器不能直接删除这些对未托管资源的引用,因为它只负责托管的资源,于是它只能依赖用户提供的 Finalize()。 |
GetType() | 这个方法返回从 System.Type 派生的类的一个实例。这个对象可以提供对象成员所属类的更多信息,包括基本类型、方法、属性等。System.Type 还提供了 .NET 的反射技术的入口点。 |
MemberwiseClone() | 它复制对象,并返回对副本的一个引用(对于值类型,就是一个装箱的引用)。注意,得到的副本是一个浅表复制,即它复制了类的所有值类型。如果类包含内嵌的引用,就只复制引用,而不复制引用的对象。这个方法是受保护的。所以不能用于复制外部的对象。该方法不是虚方法,所以不能重写它的实现代码。 |