程序的数据和功能被组织为逻辑上相关的数据项和函数的封装集合,并称为类。
类是一个能存储数据并执行代码的数据结构。它包含数据成员和函数成员。
表-类成员的类型
数据成员存储数据 | 函数成员执行代码 |
---|---|
字段、常量 | 方法、属性、构造函数、析构函数 运算符、索引器、事件 |
//显式和隐式字段初始化
class MyClass
{
int F1; //初始化为 0 - 值类型
string F2; //初始化为 null - 引用类型
int F3 = 25; //初始化为 25
string F4 = "abcd"; //初始化为 “abcd”
}
类的实例是引用类型,所以需要使用 new 运算符实例化:
1)只读和只写属性
2)属性与公有字段的区别
《C# 图解教程-7.10.7 属性与公有字段》书上附加内容:
如果要发布一个由其他代码引用的程序集,那么第三点将会带来一些影响。例如,有的时候开发人员可能想用共有字段代替属性,因为如果以后需要为字段的数据增加处理逻辑的话,可以再把字段改为属性。这没错,但是如果那样修改的话,所有访问这个字段的其他程序集都需要重新编译,因为字段和属性在编译后的语义不一样。另外,如果实现的是属性,那么只需要修改属性的实现,而无须重新编译访问它的其他程序集。
我需要提供一些代码例子来加强理解:
如果程序员对某一情况最初始的想法是只使用字段:
//程序集Stu
class Student
{
public int Id;
}
被其他程序集ABC...访问引用
class Other
{
public Other()
{
Student stu = new Student();
stu.Id = 2;//此处是字段
}
}
但是产品经理说,下版本有这样的需求:Student 的 Id 不能有负值。哦豁,如果按照以上的代码来设计,那估计其他访问Stu的所有程序集都必须做判断的逻辑处理。如果只是把Id字段改为属性类型,同样也得需要重新编译其他所有访问 Stu 的程序集才能生效,因为它们访问的类型都必须保持一致。否则,会报错。
所以,最开始就应该这么做:
//程序集Stu 版本1
class 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;
else
id = 0;
}
}
}
其他所有程序集访问Stu
class Other
{
public Other()
{
Student stu = new Student();
stu.Id = 2;//此处是属性,对版本1和版本和2都能访问
}
}
3)自动实现属性(简称自动属性)
4)静态属性
静态属性的访问器和所有静态成员一样,具有以下特点:
可以声明为 static 的类成员类型做了勾选标记。
表-可以声明为静态的类成员类型
数据成员(存储数据) | 函数成员(执行代码) |
---|---|
✅字段 | ✅方法 |
✅类型 | ✅属性 |
常量 | ✅构造函数 |
✅运算符 | |
索引器 | |
✅事件 |
静态构造函数初始化类的静态字段
静态构造函数与实例构造函数的区别:
虚成员 | 抽象成员 | |
---|---|---|
关键字 | 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 关键字允许把类、结构、方法或接口放在多个文件中。
分部方法分为两部分:
分部方法定义声明和实现声明的签名和返回类型必须匹配。签名和返回类型有如下特征:
//文件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();
}
}
所有的 .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() | 它复制对象,并返回对副本的一个引用(对于值类型,就是一个装箱的引用)。注意,得到的副本是一个浅表复制,即它复制了类的所有值类型。如果类包含内嵌的引用,就只复制引用,而不复制引用的对象。这个方法是受保护的。所以不能用于复制外部的对象。该方法不是虚方法,所以不能重写它的实现代码。 |