目录
每当创建类或结构时,将会调用其构造函数。 类或结构可能具有采用不同参数的多个构造函数。 使用构造函数,程序员能够设置默认值、限制实例化,并编写灵活易读的代码。
构造函数语法
构造函数是一种方法,其名称与其类型的名称相同。 其方法签名仅包含方法名称和其参数列表;它不包含返回类型。 以下示例演示一个名为
Person
的类的构造函数。
public class Person
{
private string last;
private string first;
public Person(string lastName, string firstName)
{
last = lastName;
first = firstName;
}
// Remaining implementation of Person class.
}
如果某个构造函数可以作为单个语句实现,则可以使用表达式主体定义。 以下示例定义 Location
类,其构造函数具有一个名为“name”的字符串参数。 表达式主体定义给 locationName
字段分配参数。
public class Location
{
private string locationName;
public Location(string name) => Name = name;
public string Name
{
get => locationName;
set => locationName = value;
}
}
使用构造函数
在下面的示例中,通过使用简单构造函数定义了一个名为 Taxi
的类。 然后使用 new 运算符对该类进行实例化。 在为新对象分配内存之后,new
运算符立即调用 Taxi
构造函数。
public class Taxi
{
public bool IsInitialized;
public Taxi()
{
IsInitialized = true;
}
}
class TestTaxi
{
static void Main()
{
Taxi t = new Taxi();
Console.WriteLine(t.IsInitialized);
}
}
不带任何参数的构造函数称为“无参数构造函数”。 每当使用
new
运算符实例化对象且不为new
提供任何参数时,会调用无参数构造函数。除非类是静态的,否则 C# 编译器将为无构造函数的类提供一个公共的无参数构造函数,以便该类可以实例化。
通过将构造函数设置为私有构造函数,可以阻止类被实例化,如下所示:
class NLog
{
// Private Constructor:
private NLog() { }
public static double e = Math.E; //2.71828...
}
结构类型的构造函数与类的构造函数类似,但是 structs
不包含显式无参数构造函数,因为编译器将自动提供一个显式无参数构造函数。 此构造函数会将 struct
中的每个字段初始化为默认值。 但是,只有使用 new
实例化 struct
时,才会调用此无参数构造函数。 例如,此代码使用 Int32 的无参数构造函数,因此可确保整数已初始化:
int i = new int();
Console.WriteLine(i);
但是,下面的代码会导致编译器错误,因为它不使用 new
,而且尝试使用尚未初始化的对象:
int i;
Console.WriteLine(i);
或者,可将基于
structs
的对象(包括所有内置数值类型)初始化或赋值后使用,如下面的示例所示:
int a = 44; // Initialize the value type...
int b;
b = 33; // Or assign it before using it.
Console.WriteLine("{0}, {1}", a, b);
因此无需调用值类型的无参数构造函数。
类和
structs
都可以定义带参数的构造函数。 必须通过new
语句或 base 语句调用带参数的构造函数。 类和structs
还可以定义多个构造函数,并且二者均无需定义无参数构造函数。 例如:
public class Employee
{
public int Salary;
public Employee() { }
public Employee(int annualSalary)
{
Salary = annualSalary;
}
public Employee(int weeklySalary, int numberOfWeeks)
{
Salary = weeklySalary * numberOfWeeks;
}
}
可使用下面任一语句创建此类:
Employee e1 = new Employee(30000);
Employee e2 = new Employee(500, 52);
构造函数可以使用
base
关键字调用基类的构造函数。 例如:
public class Manager : Employee
{
public Manager(int annualSalary)
: base(annualSalary)
{
//Add further instructions here.
}
}
在此示例中,在执行构造函数块之前调用基类的构造函数。
base
关键字可带参数使用,也可不带参数使用。 构造函数的任何参数都可用作base
的参数,或用作表达式的一部分。
在派生类中,如果不使用 base
关键字来显式调用基类构造函数,则将隐式调用无参数构造函数(若有)。 这意味着下面的构造函数声明等效:
public Manager(int initialData)
{
//Add further instructions here.
}
public Manager(int initialData)
: base()
{
//Add further instructions here.
}
如果基类没有提供无参数构造函数,派生类必须使用 base
显式调用基类构造函数。
构造函数可以使用 this 关键字调用同一对象中的另一构造函数。 和
base
一样,this
可带参数使用也可不带参数使用,构造函数中的任何参数都可用作this
的参数,或者用作表达式的一部分。 例如,可以使用this
重写前一示例中的第二个构造函数:
public Employee(int weeklySalary, int numberOfWeeks)
: this(weeklySalary * numberOfWeeks)
{
}
上一示例中使用 this
关键字会导致此构造函数被调用:
public Employee(int annualSalary)
{
Salary = annualSalary;
}
实例构造函数
声明一个实例构造函数,以指定在使用 new 表达式创建某个类型的新实例时所执行的代码。 要初始化静态类或非静态类中的静态变量,可以定义静态构造函数。
如以下示例所示,可以在一种类型中声明多个实例构造函数:
using System;
class Coords
{
public Coords()
: this(0, 0)
{ }
public Coords(int x, int y)
{
X = x;
Y = y;
}
public int X { get; set; }
public int Y { get; set; }
public override string ToString() => $"({X},{Y})";
}
class Example
{
static void Main()
{
var p1 = new Coords();
Console.WriteLine($"Coords #1 at {p1}");
// Output: Coords #1 at (0,0)
var p2 = new Coords(5, 3);
Console.WriteLine($"Coords #2 at {p2}");
// Output: Coords #2 at (5,3)
}
}
在上个示例中,第一个无参数构造函数调用两个参数都等于
0
的第二个构造函数。 要执行此操作,请使用this
关键字。
在派生类中声明实例构造函数时,可以调用基类的构造函数。 为此,请使用 base
关键字,如以下示例所示:
using System;
abstract class Shape
{
public const double pi = Math.PI;
protected double x, y;
public Shape(double x, double y)
{
this.x = x;
this.y = y;
}
public abstract double Area();
}
class Circle : Shape
{
public Circle(double radius)
: base(radius, 0)
{ }
public override double Area() => pi * x * x;
}
class Cylinder : Circle
{
public Cylinder(double radius, double height)
: base(radius)
{
y = height;
}
public override double Area() => (2 * base.Area()) + (2 * pi * x * y);
}
class Example
{
static void Main()
{
double radius = 2.5;
double height = 3.0;
var ring = new Circle(radius);
Console.WriteLine($"Area of the circle = {ring.Area():F2}");
// Output: Area of the circle = 19.63
var tube = new Cylinder(radius, height);
Console.WriteLine($"Area of the cylinder = {tube.Area():F2}");
// Output: Area of the cylinder = 86.39
}
}
无参数构造函数
如果某个类没有显式实例构造函数,C# 将提供可用于实例化该类实例的无参数构造函数,如以下示例所示:
using System;
public class Person
{
public int age;
public string name = "unknown";
}
class Example
{
static void Main()
{
var person = new Person();
Console.WriteLine($"Name: {person.name}, Age: {person.age}");
// Output: Name: unknown, Age: 0
}
}
该构造函数根据相应的初始值设定项初始化实例字段和属性。 如果字段或属性没有初始值设定项,其值将设置为字段或属性类型的默认值。 如果在某个类中声明至少一个实例构造函数,则 C# 不提供无参数构造函数。
私有构造函数
私有构造函数是一种特殊的实例构造函数。 它通常用于只包含静态成员的类中。 如果类具有一个或多个私有构造函数而没有公共构造函数,则其他类(除嵌套类外)无法创建该类的实例。 例如:
class NLog
{
// Private Constructor:
private NLog() { }
public static double e = Math.E; //2.71828...
}
声明空构造函数可阻止自动生成无参数构造函数。 请注意,如果不对构造函数使用访问修饰符,则在默认情况下它仍为私有构造函数。 但是,通常会显式地使用 private 修饰符来清楚地表明该类不能被实例化。
当没有实例字段或实例方法(例如 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 will generate
// 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
静态构造函数
静态构造函数用于初始化任何静态数据,或执行仅需执行一次的特定操作。 将在创建第一个实例或引用任何静态成员之前自动调用静态构造函数。
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) 调用。 可以自动调用它们。
-
用户无法控制在程序中执行静态构造函数的时间。
-
自动调用静态构造函数。 将在创建第一个实例或引用任何静态成员之前初始化类。 静态构造函数在实例构造函数之前运行。 调用(而不是分配)分配给事件或委托的静态方法时,将调用类型的静态构造函数。 如果静态构造函数类中存在静态字段变量初始值设定项,它们将以在类声明中显示的文本顺序执行。 初始值设定项紧接着执行静态构造函数之前运行。
-
如果未提供静态构造函数来初始化静态字段,会将所有静态字段初始化为其默认值,如 C# 类型的默认值中所列。
-
如果静态构造函数引发异常,运行时将不会再次调用该函数,并且类型在应用程序域的生存期内将保持未初始化。 大多数情况下,当静态构造函数无法实例化一个类型时,或者当静态构造函数中发生未经处理的异常时,将引发 TypeInitializationException 异常。 对于未在源代码中显式定义的静态构造函数,故障排除可能需要检查中间语言 (IL) 代码。
-
静态构造函数的存在将防止添加 BeforeFieldInit 类型属性。 这将限制运行时优化。
-
声明为
static readonly
的字段可能仅被分配为其声明的一部分或在静态构造函数中。 如果不需要显式静态构造函数,请在声明时初始化静态字段,而不是通过静态构造函数,以实现更好的运行时优化。 -
运行时在单个应用程序域中多次调用静态构造函数。 该调用是基于特定类型的类在锁定区域中进行的。 静态构造函数的主体中不需要其他锁定机制。 若要避免死锁的风险,请勿阻止静态构造函数和初始值设定项中的当前线程。 例如,不要等待任务、线程、等待句柄或事件,不要获取锁定,也不要执行阻止并行操作,如并行循环、
Parallel.Invoke
和并行 LINQ 查询。
用法
-
静态构造函数的一种典型用法是在类使用日志文件且将构造函数用于将条目写入到此文件中时使用。
-
静态构造函数对于创建非托管代码的包装类也非常有用,这种情况下构造函数可调用
LoadLibrary
方法。 -
也可在静态构造函数中轻松地对无法在编译时通过类型参数约束检查的类型参数强制执行运行时检查。
示例
在此示例中,类 Bus
具有静态构造函数。 创建 Bus
的第一个实例 (bus1
) 时,将调用该静态构造函数,以便初始化类。 示例输出验证即使创建了两个 Bus
的实例,静态构造函数也仅运行一次,并且在实例构造函数运行前运行。
public class Bus
{
// Static variable used by all Bus instances.
// Represents the time the first bus of the day starts its route.
protected static readonly DateTime globalStartTime;
// Property for the number of each bus.
protected int RouteNumber { get; set; }
// Static constructor to initialize the static variable.
// It is invoked before the first instance constructor is run.
static Bus()
{
globalStartTime = DateTime.Now;
// The following statement produces the first line of output,
// and the line occurs only once.
Console.WriteLine("Static constructor sets global start time to {0}",
globalStartTime.ToLongTimeString());
}
// Instance constructor.
public Bus(int routeNum)
{
RouteNumber = routeNum;
Console.WriteLine("Bus #{0} is created.", RouteNumber);
}
// Instance method.
public void Drive()
{
TimeSpan elapsedTime = DateTime.Now - globalStartTime;
// For demonstration purposes we treat milliseconds as minutes to simulate
// actual bus times. Do not do this in your actual bus schedule program!
Console.WriteLine("{0} is starting its route {1:N2} minutes after global start time {2}.",
this.RouteNumber,
elapsedTime.Milliseconds,
globalStartTime.ToShortTimeString());
}
}
class TestBus
{
static void Main()
{
// The creation of this instance activates the static constructor.
Bus bus1 = new Bus(71);
// Create a second bus.
Bus bus2 = new Bus(72);
// Send bus1 on its way.
bus1.Drive();
// Wait for bus2 to warm up.
System.Threading.Thread.Sleep(25);
// Send bus2 on its way.
bus2.Drive();
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Sample output:
Static constructor sets global start time to 3:57:08 PM.
Bus #71 is created.
Bus #72 is created.
71 is starting its route 6.00 minutes after global start time 3:57 PM.
72 is starting its route 31.00 minutes after global start time 3:57 PM.
*/