实现专用、静态和嵌套类
实例构造函数用于指定在使用 new 运算符创建类的新实例时执行的代码。 当实例构造函数使用 public 访问修饰符时,可从应用程序中的任何其他代码访问生成的对象。 此类对对象及其成员的访问通常是可取的,但有时需要限制对类或类成员的访问。
当需要隐藏或减少类的公开时,可以使用访问修饰符来控制类及其成员的可见性。
访问修饰符
所有类型和类型成员都具有可访问性级别,用于控制它们是否可从编译的应用程序中的其他代码(或其他程序集)访问。
直接在命名空间中声明的类可以具有 public、internal 或 file 访问权限。 如果未指定访问修饰符,则默认情况下会 internal 访问分配类。
使用以下访问修饰符在声明类型或成员时指定其可访问性:
-
public:任何程序集中的代码都可以访问此类型或成员。 包含类型的辅助功能级别控制该类型的公共成员的可访问性级别。 -
private:只有在同一class或struct中声明的代码才能访问此成员。 -
protected:只有同一class或派生class中的代码才能访问此类型或成员。 -
internal:只有同一程序集中的代码才能访问此类型或成员。 -
protected internal:只有同一程序集或另一程序集派生类中的代码才能访问此类型或成员。 -
private protected:只有同一程序集和同一类或派生类中的代码才能访问类型或成员。 -
file:只有同一文件中的代码可以访问类型或成员。
类型的 record 修饰符会导致编译器合成额外的成员。
record 修饰符不会影响 record class 或 record struct的默认可访问性。
私有类构造函数
专用构造函数是一个特殊的实例构造函数。 专用构造函数通常用于仅包含静态成员的类。 如果类具有一个或多个私有构造函数,并且没有公共构造函数,则其他类(嵌套类除外)无法创建类的实例。 例如:
class NLog
{
// Private Constructor:
private NLog() { }
public static double e = Math.E; //2.71828...
}
空构造函数的声明可防止自动生成无参数构造函数。 如果未为构造函数指定访问修饰符,则默认为 private。 但是,应使用 private 修饰符来阐明无法实例化类。
当没有实例字段或方法(例如 .NET 库中的 Math 类)或调用方法以获取类实例时,专用构造函数用于防止创建类的实例。 如果类中的所有方法都是静态的,请考虑将完整的类静态化。
以下示例演示使用专用构造函数的类。
public class Counter
{
private Counter() { }
public static int currentCount;
public static int IncrementCount()
{
return ++currentCount;
}
}
class TestCounter
{
static void Main()
{
// If you uncomment the following statement, it generates
// an error because the constructor is inaccessible:
// Counter aCounter = new Counter(); // Error
Counter.currentCount = 100;
Counter.IncrementCount();
Console.WriteLine("New count: {0}", Counter.currentCount);
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
// Output: New count: 101
如果从示例中取消注释以下语句,编译器将生成生成错误。 编译器了解私有构造函数不可访问。
// Counter aCounter = new Counter(); // Error
静态类
静态类基本上与非静态类相同,有一个重要区别:无法实例化静态类。 换句话说,不能使用 new 运算符从静态类创建变量。 虽然无法创建类的实例,但可以通过引用类名来访问静态类的成员。 例如,如果有一个名为 UtilityClass 的静态类具有名为 MethodA的公共静态方法,则可以使用类和方法名称的组合调用该方法。 以下示例显示了此语法:
UtilityClass.MethodA();
静态类可用作对输入参数进行作的方法的容器,无需 get 或 set 任何内部实例字段。
例如,在 .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之外,它们不能继承自任何类或接口。 静态类不能包含实例构造函数。 但是,它们可以包含 static 构造函数。 如果类包含 static 需要非普通初始化的成员,则非静态类还应定义 static 构造函数。
下面是一个静态类的示例,该类包含两种方法,该方法将温度从摄氏度转换为华氏度,从华氏度转换为摄氏度:
public static class TemperatureConverter
{
public static double CelsiusToFahrenheit(string temperatureCelsius)
{
// Convert argument to double for calculations.
double celsius = Double.Parse(temperatureCelsius);
// Convert Celsius to Fahrenheit.
double fahrenheit = (celsius * 9 / 5) + 32;
return fahrenheit;
}
public static double FahrenheitToCelsius(string temperatureFahrenheit)
{
// Convert argument to double for calculations.
double fahrenheit = Double.Parse(temperatureFahrenheit);
// Convert Fahrenheit to Celsius.
double celsius = (fahrenheit - 32) * 5 / 9;
return celsius;
}
}
class TestTemperatureConverter
{
static void Main()
{
Console.WriteLine("Please select the convertor direction");
Console.WriteLine("1. From Celsius to Fahrenheit.");
Console.WriteLine("2. From Fahrenheit to Celsius.");
Console.Write(":");
string? selection = Console.ReadLine();
double F, C = 0;
switch (selection)
{
case "1":
Console.Write("Please enter the Celsius temperature: ");
F = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine() ?? "0");
Console.WriteLine("Temperature in Fahrenheit: {0:F2}", F);
break;
case "2":
Console.Write("Please enter the Fahrenheit temperature: ");
C = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine() ?? "0");
Console.WriteLine("Temperature in Celsius: {0:F2}", C);
break;
default:
Console.WriteLine("Please select a convertor.");
break;
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Example Output:
Please select the convertor direction
1. From Celsius to Fahrenheit.
2. From Fahrenheit to Celsius.
:2
Please enter the Fahrenheit temperature: 20
Temperature in Celsius: -6.67
Press any key to exit.
*/
静态成员
非静态类可以包含静态方法、字段、属性或事件。 即使不存在该类的实例,静态成员也能在类上调用。 静态成员始终由类名而不是实例名称访问。 无论创建类的实例数如何,只有一个静态成员的副本存在。 静态方法和属性无法访问其包含类型的非静态字段和事件,并且无法访问任何对象的实例变量,除非它显式传入方法参数。
声明具有一些静态成员的非静态类比将整个类声明为静态类更为典型。 静态字段的两种常见用途是保留实例化的对象数的计数,或存储必须在所有实例之间共享的值。
静态方法可以重载,但不能重写,因为它们属于该类,并且不属于类的任何实例。
虽然不能将字段声明为 static const,但 const 字段在其行为中本质上是静态的。 它属于类型,不属于该类型的实例。 因此,可以使用用于静态字段的相同 const 表示法来访问 ClassName.MemberName 字段。 不需要任何对象实例。
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 nonstatic fields and properties...
}
在首次访问静态成员之前和静态构造函数(如果有)调用静态成员之前,将初始化静态成员。 若要访问静态类成员,请使用类的名称而不是变量名称来指定成员的位置,如以下示例所示:
Automobile.Drive();
int i = Automobile.NumberOfWheels;
如果类包含静态字段,请提供一个静态构造函数,用于在加载类时初始化它们。
对静态方法的调用生成公共中间语言(CIL)的调用指令,而对实例方法的调用将生成一个 callvirt 指令,该指令还会检查 null 对象引用。 但是,在大多数情况下,两者之间的性能差异并不重要。
嵌套类
在另一个类中定义的 class 类型称为嵌套类。 例如:
public class Container
{
class Nested
{
Nested() { }
}
}
嵌套类的可访问性默认为 private。 这意味着嵌套类只能从其包含的类访问。 在前面的示例中,外部类型无法访问 Nested 类。
可以指定访问修饰符来定义嵌套类型的可访问性,如下所示:
- 类的嵌套类型可以是
public、protected、internal、protected internal、private或private protected。
但是,在密封类中定义 protected、protected internal 或 private protected 嵌套类会生成编译器警告 CS0628,“在密封类中声明的新受保护成员”。
谨慎
请注意,使嵌套类型在外部可见违反了代码质量规则 CA1034“嵌套类型不应可见”。
以下示例将 Nested 类公开:
public class Container
{
public class Nested
{
Nested() { }
}
}
嵌套类或内部类可以访问包含类或外部类。 若要访问包含类,请将其作为参数传递给嵌套类的构造函数。 例如:
public class Container
{
public class Nested
{
private Container? parent;
public Nested()
{
}
public Nested(Container parent)
{
this.parent = parent;
}
}
}
嵌套类有权访问其包含类可访问的所有成员。 它可以访问包含类的私有和受保护的成员,包括任何继承的受保护成员。
在前面的声明中,类 Nested 的全名 Container.Nested。 这是用于创建嵌套类的新实例的名称,如下所示:
Container.Nested nest = new Container.Nested();