第十单元 面向对象二:静态类与静态成员
我们天天都在使用
Console.WriteLine("Hello world"); Console.ReadLine(""); Arrays.Sort()
为什么Console 类 调用 方法不需要实例化而可以直接调用呢?
我们可以查看Console的源码发现Console类定义如下:
public static class Console { // ... }
我们发现 Console 类 前面有个 static 关键字修饰, 我们把 static 修饰的类 叫作 静态类
1. 静态类
static关键字的作用
-
static是静态的意思,可以修饰成员变量和成员方法。
-
static修饰成员变量表示该成员变量只在内存中只存储一份,可以被共享访问、修改。
public static class MyConsole { // 静态构造方法 static MyConsole() { } // 静态变量 public static string Title { get; set; } public static string Description { get; set; } // 静态方法 public static void WriteLine(params string[] s) { } } class Program { static void Main(string[] args) { MyConsole.Title = "Hello,静态类"; MyConsole.WriteLine("静态方法调用"); } }
静态类基本上与非静态类相同,但存在一个差异:静态类无法实例化。 换句话说,无法使用
与所有类类型的情况一样,加载引用该类的程序时,.NET 运行时会加载静态类的类型信息。 程序无法确切指定类加载的时间。 但是,可保证进行加载,以及在程序中首次引用类之前初始化其字段并调用其静态构造函数。 静态构造函数只调用一次,在程序所驻留的应用程序域的生存期内,静态类会保留在内存中。
以下列表提供静态类的主要功能:
-
只包含静态成员。
-
无法进行实例化。
-
会进行密封。
-
不能包含
因此,创建静态类基本上与创建只包含静态成员和私有构造函数的类相同。 私有构造函数可防止类进行实例化。 使用静态类的优点是编译器可以进行检查,以确保不会意外地添加任何实例成员。 编译器可保证无法创建此类的实例。
静态类会进行密封,因此不能继承。 它们不能继承自任何类(除了
2. 非静态类的静态成员
非静态类
不被static 关键字修饰的类称为非静态类.
非静态类可以包含静态方法、字段、属性或事件。 即使未创建类的任何实例,也可对类调用静态成员。 静态成员始终按类名(而不是实例名称)进行访问。 静态成员只有一个副本存在(与创建的类的实例数无关)。 静态方法和属性无法在其包含类型中访问非静态字段和事件,它们无法访问任何对象的实例变量,除非在方法参数中显式传递它。
更典型的做法是声明具有一些静态成员的非静态类(而不是将整个类都声明为静态)。 静态字段的两个常见用途是保留已实例化的对象数的计数,或是存储必须在所有实例间共享的值。
静态方法可以进行重载,但不能进行替代,因为它们属于类,而不属于类的任何实例。
虽然字段不能声明为 static const
,不过 static const
字段在其行为方面本质上是静态的。 它属于类型,而不属于类型的实例。 因此,可以使用用于静态字段的相同 ClassName.MemberName
表示法来访问 const
字段。 无需进行对象实例化。
C# 不支持静态局部变量(即在方法范围中声明的变量)。
可在成员的返回类型之前使用 static
关键字声明静态类成员,如下面的示例所示:
public class Cat // 非静态类 { public string Name { get; set; } // 实例变量,属于对象 public static int Leg { get; set; } // 静态成员变量,, 属于类 // 重载静态方法run, 属于类 public static void Run() { // Console.WriteLine(this.Name);// 报错,静态方法中,不能访问实例成员 Console.WriteLine($"{Leg} 条腿的猫在跑步");// 正确,静态类中可以访问静态成员变量 } // 重载静态方法run, 属于类 public static void Run(int speed) { Console.WriteLine($"{Leg} 条腿的猫在跑步,速度是:{speed}"); } // 实例方法,属于对象 public void Eat() { // 实例方法既可以访问成员变量,也可以访问静态变量 Console.WriteLine($"{Leg} 条腿的 {this.Name} 在抢狗粮吃。") ; } }
在首次访问静态成员之前以及在调用构造函数(如果有)之前,会初始化静态成员。 若要访问静态类成员,请使用类的名称(而不是变量名称)指定成员的位置,如下面的示例所示:
static void Main(string[] args) { Cat.Leg = 4; Cat.Run(30); Cat c = new Cat(); c.Eat(); }
3. 静态构造方法
静态构造函数用于初始化任何
class SimpleClass { // Static variable that must be initialized at run time. static readonly long baseline; // Static constructor is called at most one time, before any // instance constructor is invoked or member is accessed. static SimpleClass() { baseline = DateTime.Now.Ticks; } }
静态构造函数具有以下属性:
-
静态构造函数不使用访问修饰符或不具有参数。
-
类或结构只能有一个静态构造函数。
-
静态构造函数不能继承或重载。
-
静态构造函数不能直接调用,并且仅应由公共语言运行时 (CLR) 调用。 可以自动调用它们。
-
用户无法控制在程序中执行静态构造函数的时间。
-
自动调用静态构造函数。 它在创建第一个实例或引用该类(不是其基类)中声明的任何静态成员之前初始化
-
如果未提供静态构造函数来初始化静态字段,会将所有静态字段初始化为其默认值,如
-
如果静态构造函数引发异常,运行时将不会再次调用该函数,并且类型在应用程序域的生存期内将保持未初始化。 大多数情况下,当静态构造函数无法实例化一个类型时,或者当静态构造函数中发生未经处理的异常时,将引发
-
静态构造函数的存在将防止添加
-
声明为
static readonly
的字段可能仅被分配为其声明的一部分或在静态构造函数中。 如果不需要显式静态构造函数,请在声明时初始化静态字段,而不是通过静态构造函数,以实现更好的运行时优化。 -
运行时在单个应用程序域中多次调用静态构造函数。 该调用是基于特定类型的类在锁定区域中进行的。 静态构造函数的主体中不需要其他锁定机制。 若要避免死锁的风险,请勿阻止静态构造函数和初始值设定项中的当前线程。 例如,不要等待任务、线程、等待句柄或事件,不要获取锁定,也不要执行阻止并行操作,如并行循环、
Parallel.Invoke
和并行 LINQ 查询。
单例模式实现
class MyDbContext { private static MyDbContext _instance; // 禁止外部实例化 private MyDbContext() { } public static string DbName; public static string UserName; public static string Password; // 静态构造方法 static MyDbContext() { DbName = "SqlServer"; UserName = "sa"; Password = "123456"; // 实例化一次 _instance = new MyDbContext(); } // 对外提供访问的路径 public MyDbContext Instance { get { return _instance; } } public void Connection() { } public void Abort() { } }
4. 作业
-
定义一个静态类
MyMath
-
在
MyMath
类中 实现两个参数的方法:加,减,乘,除,平均数 -
重载 :加,减,乘,除,平均数
-
定义一个非静态类
CommonUtils
, 任意声明两个实例成员变量与静态成员变量 -
在
CommonUtils
类中,声明一个实例方法,并输出 上述的实例成员变量与静态成员变量。 -
在
CommonUtils
类中, 声明一个静态方法,并输出静态成员变量。