定义为 class
的类型是引用类型。 在运行时,如果声明引用类型的变量,此变量就会一直包含值 null
,直到使用 new
运算符显式创建类实例,或直到为此变量分配可能已在其他位置创建的兼容类型的对象
//Declaring an object of type MyClass.
MyClass mc = new MyClass();
//Declaring another object of the same type, assigning it the value of the first object.
MyClass mc2 = mc;
创建对象时,在该托管堆上为该特定对象分足够的内存,并且该变量仅保存对所述对象位置的引用。 对象使用的内存由 CLR
的自动内存管理功能(称为“垃圾回收”)回收
abstract
(抽象类)在类声明中使用 abstract
修饰符来指示某个类仅用作其他类的基类,而不用于自行进行实例化。 标记为抽象的成员必须由派生自抽象类的非抽象类来实现
示例:
abstract class Shape
{
public abstract int GetArea();
}
class Square : Shape
{
private int _side;
public Square(int n) => _side = n;
// GetArea method is required to avoid a compile-time error.
public override int GetArea() => _side * _side;
static void Main()
{
var sq = new Square(12);
Console.WriteLine($"Area of the square = {sq.GetArea()}");
}
}
// Output: Area of the square = 144
抽象类具有以下功能:
抽象类不能实例化。
抽象类可能包含抽象方法和访问器。
无法使用 sealed
修饰符来修改抽象类,因为两个修饰符的含义相反。 sealed
修饰符阻止类被继承,而 abstract 修饰符要求类被继承。
派生自抽象类的非抽象类,必须包含全部已继承的抽象方法和访问器的实际实现。
在方法或属性声明中使用 abstract
修饰符,以指示该方法或属性不包含实现。
抽象方法具有以下功能:
抽象方法是隐式的虚拟方法。
只有抽象类中才允许抽象方法声明。
由于抽象方法声明不提供实际的实现,因此没有方法主体;方法声明仅以分号结尾,且签名后没有大括号 ({ })。 例如:
public abstract void MyMethod();
实现由方法 override
提供,它是非抽象类的成员。
static
或 virtual
修饰符是错误的。除了声明和调用语法方面不同外,抽象属性的行为与抽象方法相似。
在静态属性上使用 abstract
修饰符是错误的。
通过包含使用 override
修饰符的属性声明,可在派生类中重写抽象继承属性。
抽象类必须为所有接口成员提供实现。
实现接口的抽象类有可能将接口方法映射到抽象方法上。 例如:
interface I
{
void M();
}
abstract class C : I
{
public abstract void M();
}
示例 2
在此示例中,类 DerivedClass
派生自抽象类 BaseClass
。 抽象类包含抽象方法 AbstractMethod
,以及两个抽象属性 X
和 Y
// Abstract class
abstract class BaseClass
{
protected int _x = 100;
protected int _y = 150;
// Abstract method
public abstract void AbstractMethod();
// Abstract properties
public abstract int X { get; }
public abstract int Y { get; }
}
class DerivedClass : BaseClass
{
public override void AbstractMethod()
{
_x++;
_y++;
}
public override int X // overriding property
{
get
{
return _x + 10;
}
}
public override int Y // overriding property
{
get
{
return _y + 10;
}
}
static void Main()
{
var o = new DerivedClass();
o.AbstractMethod();
Console.WriteLine($"x = {o.X}, y = {o.Y}");
}
}
// Output: x = 111, y = 161
在前面的示例中,如果你尝试通过使用如下语句来实例化抽象类:
BaseClass bc = new BaseClass(); // Error
将遇到一个错误,告知编译器无法创建抽象类BaseClass
的实例。
sealed
(封闭类)应用于某个类时,sealed
修饰符可阻止其他类继承自该类。 在下面的示例中,类 B
继承自类 A
,但没有类可以继承自类 B
。
class A {}
sealed class B : A {}
还可以对替代基类中的虚方法或属性的方法或属性使用 sealed
修饰符。 这使你可以允许类派生自你的类并防止它们替代特定虚方法或属性
示例
class X
{
protected virtual void F() { Console.WriteLine("X.F"); }
protected virtual void F2() { Console.WriteLine("X.F2"); }
}
class Y : X
{
sealed protected override void F() { Console.WriteLine("Y.F"); }
protected override void F2() { Console.WriteLine("Y.F2"); }
}
class Z : Y
{
// Attempting to override F causes compiler error CS0239.
// protected override void F() { Console.WriteLine("Z.F"); }
// Overriding F2 is allowed.
protected override void F2() { Console.WriteLine("Z.F2"); }
}
在下面的示例中,Z
继承自 Y
,但 Z
无法替代在 X
中声明并在 Y
中密封的虚函数 F
。
static
(静态类)静态类基本上与非静态类相同,但存在一个差异:静态类无法实例化。 换句话说,无法使用 new
运算符创建类类型的变量。 由于不存在任何实例变量,因此可以使用类名本身访问静态类的成员。 例如,如果你具有一个静态类,该类名为 UtilityClass
,并且具有一个名为 MethodA
的公共静态方法,如下面的示例所示:
UtilityClass.MethodA();
静态类可以用作只对输入参数进行操作并且不必获取或设置任何内部实例字段的方法集的方便容器。 例如,在 .NET
类库中,静态 System.Math
类包含执行数学运算,而无需存储或检索对 Math
类特定实例唯一的数据的方法。 即,通过指定类名和方法名称来应用类的成员,如下面的示例所示。
double dub = -3.14;
Console.WriteLine(Math.Abs(dub));
Console.WriteLine(Math.Floor(dub));
Console.WriteLine(Math.Round(Math.Abs(dub)));
// Output:
// 3.14
// -4
// 3
与所有类类型的情况一样,加载引用该类的程序时,.NET
运行时会加载静态类的类型信息。 程序无法确切指定类加载的时间。 但是,可保证进行加载,以及在程序中首次引用类之前初始化其字段并调用其静态构造函数。 静态构造函数只调用一次,在程序所驻留的应用程序域的生存期内,静态类会保留在内存中。
以下列表提供静态类的主要功能:
只包含静态成员。
无法进行实例化。
会进行密封。
不能包含实例构造函数。
因此,创建静态类基本上与创建只包含静态成员和私有构造函数的类相同。 私有构造函数可防止类进行实例化。 使用静态类的优点是编译器可以进行检查,以确保不会意外地添加任何实例成员。 编译器可保证无法创建此类的实例。
静态类会进行密封,因此不能继承。 它们不能继承自任何类或接口(除了 Object)。 静态类不能包含实例构造函数。 但是,它们可以包含静态构造函数。 如果非静态类包含了需要进行有意义的初始化的静态成员,则它也应该定义一个静态构造器。
非静态类可以包含静态方法、字段、属性或事件。 即使未创建类的任何实例,也可对类调用静态成员。 静态成员始终按类名(而不是实例名称)进行访问。 静态成员只有一个副本存在(与创建的类的实例数无关)。 静态方法和属性无法在其包含类型中访问非静态字段和事件,它们无法访问任何对象的实例变量,除非在方法参数中显式传递它。
更典型的做法是声明具有一些静态成员的非静态类(而不是将整个类都声明为静态)。 静态字段的两个常见用途是保留已实例化的对象数的计数,或是存储必须在所有实例间共享的值。
静态方法可以进行重载,但不能进行替代,因为它们属于类,而不属于类的任何实例。
C#
不支持静态局部变量(即在方法范围中声明的变量)。
可在成员的返回类型之前使用 static 关键字声明静态类成员,如下面的示例所示:
public class Automobile
{
public static int NumberOfWheels = 4;
public static int SizeOfGasTank
{
get
{
return 15;
}
}
public static void Drive() { }
public static event EventType? RunOutOfGas;
// Other non-static fields and properties...
}
在首次访问静态成员之前以及在调用构造函数(如果有)之前,会初始化静态成员。 若要访问静态类成员,请使用类的名称(而不是变量名称)指定成员的位置,如下面的示例所示:
Automobile.Drive();
int i = Automobile.NumberOfWheels;
如果类包含静态字段,则提供在类加载时初始化它们的静态构造函数。
对静态方法的调用会采用 Microsoft
中间语言 (MSIL
) 生成调用指令,而对实例方法的调用会生成 callvirt
指令,该指令还会检查是否存在 null
对象引用。 但是在大多数时候,两者之间的性能差异并不显著。