写代码的时候遇到了一个问题,定义了一个数据连接相关的静态类,
public static class SQLConnect
{
// 数据库名
public static string DBName = "dmanager";
// 数据库服务器Ip
public static string DBIp = "localhost";
// 数据库服务器端口
public static string DBPort = "3306";
// 连接参数
public static string ConnectionStatement = string.Format("server={0};port={1};user=root;password=666;database={2};"
, DBIp, DBPort, DBName);
}
由于数据库的服务器可能发生变化,所以我在登录的时候需要添加一个配置服务器连接的功能。主要就是对服务器Ip端口进行配置,可以去连接不同的服务器。我想通过在一个静态类中定义一个静态字符串变量来实现这个功能(因为在我认知中,C#的静态变量有全局变量的意味,我在登录时修改静态变量的值,就可以在后续连接的时候连接修改后的值对应的服务器)。
但是,实验后发现Ip和端口的值变了,但ConnectionStatement的值并没有发生变化。
_setIpCommand = new RelayCommand(() =>
{
MessageBox.Show(DBIp);
DBIp = this.ServerIp;
MessageBox.Show(DBIp);
DBPort = this.ServerPort;
MessageBox.Show(ConnectionStatement);
});
当然,从结果来看,可以隐隐约约猜到哪里不对(比如静态成员变量是否只初始化了一次之类的)但是确切原因还是得学习一下后再下定论。
静态类与非静态类基本上是相同的,但有一点明显不同:静态类无法被实例化。换言之,你无法使用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运行环境将加载静态类的类型信息。程序无法确切地指定何时加载类。但是,在程序中第一次引用类之前,得保证加载它、初始化它的字段并且调用它的静态构造函数。静态构造函数仅调用一次,静态类在程序所在的应用程序域(Application domain)的生命周期内一直保存在内存中。
注意:
要创建一个只允许一个实例存在的非静态的类,请参阅C#中单例(singleton)实现的相关资料。
下面列出了一些静态类的主要特征:
因此,创建静态类与创建只包含静态成员和私有构造函数的类基本相同。私有构造函数也会阻止类被实例化。使用静态类的好处是,编译器可以检查,以确保没有意外添加实例成员。编译器还能保证类的实例无法被创建。
静态类是密封的,因此不能被继承。同时也不能继承除Object以外的任何类。静态类无法包含实例构造函数。不过,可以包含静态构造函数。如果非静态类包含需要非平凡初始化(non-trivial initialization,大概指不是在类中写死的)的静态成员,则还应定义静态构造函数(详细信息看静态构造函数相关内容)。
下面示例,有一个包含两个方法的静态类,两个方法分别将温度从摄氏度转为华氏度和从华氏度转为摄氏度:
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字段可以通过使用与静态字段相同的 类名.成员名 来访问,不需要对象实例。
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;
如果你的类包含了静态字段,你可以提供一个静态构造函数来在类被加载时初始化它们。
静态方法的调用会在MSIL(Microsoft Intermediate Language,微软中间语言)中生成调用指令,而对实例方法的调用会生成callvirt指令,该指令还会检查空对象引用。大多数情况下,两者之间的性能差异并不显著。
类的成员可以是静态成员或实例成员。
注意:一般来说,静态成员看作是属于类的,而实例成员看作是属于对象(类的实例)的,这点很有用。
当字段、方法、属性、事件、操作符或构造器的声明包含了static修饰符,就声明为了静态成员了。此外,常量或类型声明隐式声明为静态成员。静态成员具有以下特点:
当字段、方法、属性、事件、索引器、构造器或终结器不含static修饰符时,它就被声明为了实例成员。(实例成员有时也叫非静态成员)实例成员有以下特性:
下面例子演示并说明了静态成员和实例成员的访问规则:
class Test
{
int x;
static int y;
void F()
{
x = 1; // Ok, same as this.x = 1
y = 1; // Ok, same as Test.y = 1
}
static void G()
{
x = 1; // Error, cannot access this.x
y = 1; // Ok, same as Test.y = 1
}
static void Main()
{
Test t = new Test();
t.x = 1; // Ok
t.y = 1; // Error, cannot access static member through instance
Test.x = 1; // Error, cannot access instance member through type
Test.y = 1; // Ok
}
}
F方法展示了一个实例函数成员,simple_name可以用来访问实例成员和静态成员。G方法是一个静态函数成员,通过simple_name来访问实例成员时,它会报编译时错误。Main方法表明了实例成员应该通过实例去访问,静态成员应该通过类来访问。(simple_name在这里可以暂时理解为类中的成员标识名)
看下来似乎文章开头的问题没有明显的答案,但有一句话已经很接近实际现象了,
静态成员会在第一次访问之前被初始化,如果有静态构造函数,则在调用静态构造函数之前初始化。
想象一下,如果你new了一个对象,内部触发了构造函数,构造函数中用了一些字段属性来做运算进行该对象的初始化。后来你修改了其中一些做运算用到的属性值,那么那些在构造函数代码中受影响的字段或属性会自动发生变化吗?当然是不会。这边也是一样的道理,如果把string.format()看作是该静态字段的构造函数,那也就初始化的时候运行了一次,后面你修改相关变量,它才不会自动变化了。
所以我以为这就是答案。那如果我想修改,应该怎么做呢?
如果还是沿用这种思路来实现,应该定义一个修改ConnectionStatement的静态方法,每次更改相关变量时,去调用它。