创建类构造函数并实例化对象

已完成

除了类属性和方法,类定义还包括用于初始化新对象的构造函数(类实例)。

类构造函数

类构造函数是一个与其类型同名的方法(构造函数方法使用与类相同的名称)。

有两种类型的类构造函数:

  • 实例构造函数。 实例构造函数用于在创建对象时创建和初始化任何实例字段变量。
  • 静态构造函数。 静态构造函数用于初始化任何静态数据,或执行需要仅执行一次的特定作。 在创建第一个实例或引用任何静态成员之前,会自动调用静态构造函数。

类构造函数默认为实例构造函数。

实例构造函数语法

实例构造函数使用与类相同的名称声明,并且不包含返回类型。 构造函数的方法签名可以包括可选的访问修饰符、方法名称及其参数列表。 构造函数的方法签名不包括返回类型。

以下示例显示了名为 Person的类的简单构造函数:


public class Person
{
    public Person()
    {
        // Field initialization and constructor logic goes here.

    }

    // Remaining implementation of Person class.
}

类可以有多个构造函数。 当类具有多个构造函数时,构造函数通常采用不同的参数。

以下示例演示一个名为 Person 的类,其中包含两个构造函数。


public class Person
{

    public Person()
    {
        // Field initialization and constructor logic goes here.
        string name = "Person One";
        Console.WriteLine($"Person created: {name}");

    }

    public Person(string fName, string lName)
    {
        string name = fName + " " + lName;

        Console.WriteLine($"Person created: {name}");
   }

   // Remaining implementation of Person class.
}

使用类构造函数实例化对象

使用 new 关键字实例化对象时,.NET 运行时在类定义中调用关联的实例构造函数,并为该对象分配内存。

在以下代码片段中,Person 类定义一个简单的实例构造函数。 Program 类包括一个 Main 方法,该方法使用 new 运算符创建名为 Personperson1 实例。 运行时在为新对象分配内存后立即调用 Person 构造函数。


public class Person
{
    public Person()
    {
        // Field initialization and constructor logic goes here.

    }
}

static class Program
{
    // the Main method is the entry point of the program.
    static void Main()
    {
        Person person1 = new Person();
    }
}

具有和不使用参数的构造函数

不采用任何参数的构造函数称为无参数构造函数。 当使用 new 运算符实例化对象且未向构造函数提供任何参数时,运行时将调用无参数构造函数。

注意

除非类是静态的,否则没有构造函数的类由 C# 编译器提供公共无参数构造函数,以便启用类实例化。

类通常定义采用参数的构造函数。 必须使用 new 运算符或基语句调用采用参数的构造函数。 类可以定义一个或多个构造函数。

以下代码片段显示了一个名为 Person 的类,其中包含三个构造函数:


public class Person
{

    public Person()
    {
        // Field initialization and constructor logic goes here.

        Console.WriteLine("An instance of the Person class is being instantiated without name or age parameters.");
    }

    public Person(string name)
    {
        // Field initialization and constructor logic goes here.

        Console.WriteLine($"An instance of the Person class is being instantiated using a name ({name}) parameter.");
    }

    public Person(string name, int age)
    {
        // Field initialization and constructor logic goes here.

        Console.WriteLine($"An instance of the Person class is being instantiated using name ({name}) and age ({age}) parameters.");
    }
}

static class Program
{
    // the Main method is the entry point of the program.
    static void Main()
    {
        Person person1 = new Person();
        Person person2 = new Person("Person Two");
        Person person3 = new Person("Person Three", 30);
    }
}

没有构造函数的类

如果类没有显式实例构造函数,C# 提供了一个可用于实例化该类实例的无参数构造函数,如以下示例所示:


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# 不提供无参数构造函数。

使用构造函数参数初始化类数据

传递给构造函数的参数是构造函数的本地参数。 参数通常用于初始化类的数据字段。

以下代码片段显示了一个名为 Person 的类,其中包含用于初始化 personNamepersonAge 字段的构造函数:


public class Person
{
    public string personName;
    public string personAge;

    public Person()
    {
        // Field initialization and constructor logic goes here.
        personName = "unknown";
        personAge = "unknown";
    }

    public Person(string name)
    {
        // Field initialization and constructor logic goes here.
        personName = name;
        personAge = "unknown";
    }

    public Person(string name, int age)
    {
        // Field initialization and constructor logic goes here.
        personName = name;
        personAge = age.ToString();
    }
}

static class Program
{
    // the Main method is the entry point of the program.
    static void Main()
    {
        Person person1 = new Person();
        Person person2 = new Person("Person Two");
        Person person3 = new Person("Person Three", 30);

        Console.WriteLine($"Person 1 Name: {person1.personName} Age: {person1.personAge}");
        Console.WriteLine($"Person 2 Name: {person2.personName} Age: {person2.personAge}");
        Console.WriteLine($"Person 3 Name: {person3.personName} Age: {person3.personAge}");
    }
}

在前面的示例中,Person 类使用三个构造函数定义。 第一个构造函数将 personNamepersonAge 字段初始化为 "unknown"。 第二个构造函数将 personName 字段初始化为 name 参数中传递的值,并将 personAge 字段初始化为 "unknown"。 第三个构造函数将 personNamepersonAge 字段分别初始化 nameage 参数中传递的值。

由于字段是公共的,因此可以直接从 Main 方法访问这些字段。 代码运行时,将生成以下输出:


Person 1 Name: unknown Age: unknown
Person 2 Name: Person Two Age: unknown
Person 3 Name: Person Three Age: 30

表达式正文定义

如果构造函数可以作为单个语句实现,则可以使用 表达式正文定义 在实现构造函数时向类成员分配参数。

例如,以下构造函数使用传递给 modelName 参数的值初始化 model 字段:


public class Car
{
    public string modelName;

    public Car(string model) => modelName = model;
    
}

Car 类具有单个公共字段,modelName,类型为 stringmodelName 字段旨在存储汽车型号的名称。

Car 类还包括一个构造函数,该构造函数采用名为 model的单个字符串参数。 构造函数使用表达式正文定义(=> 语法表示)初始化 modelName 字段,并将值传递给 model 参数。 这意味着,当实例化新的 Car 对象时,modelName 字段将设置为作为构造函数的参数提供的值。

正如术语 表达式 所暗示的,=> 运算符的右侧是表达式,不限于简单的赋值语句。 表达式可以是返回值的任何有效 C# 表达式。

以下代码片段演示如何实现执行简单计算的表达式正文定义:


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);

静态构造函数

静态构造函数用于初始化任何静态数据,或执行需要仅执行一次的特定作。 在创建第一个实例或引用任何静态成员之前,系统会自动调用它。 静态构造函数最多调用一次。

以下代码片段显示了实现静态字段和静态构造函数的 Person 类的更新版本:


public class Person
{
    public string personName;
    public string personAge;

    // Static field
    public static string defaultName;
    public static string defaultAge;

    // Static constructor
    static Person()
    {
        // Static field initialization
        defaultName = "unknown";
        defaultAge = "unknown";
    }

    public Person()
    {
        // Field initialization and constructor logic goes here.
        personName = defaultName;
        personAge = defaultAge;
    }

    public Person(string name)
    {
        // Field initialization and constructor logic goes here.
        personName = name;
        personAge = defaultAge;
    }

    public Person(string name, int age)
    {
        // Field initialization and constructor logic goes here.
        personName = name;
        personAge = age.ToString();
    }
}

更新的 Person 类有两个实例字段,personNamepersonAge,两个实例字段的类型均为 string。 这些字段分别存储人员的姓名和年龄。

该类还定义了两个静态字段,defaultNamedefaultAge,也是类型 string。 静态字段在类的所有实例之间共享,并且仅初始化一次。 在这种情况下,静态字段用于提供 personNamepersonAge 字段的默认值。

静态构造函数 static Person() 负责初始化静态字段。 它将 defaultName 设置为 "unknown",并将 defaultAge 设置为 "unknown"。 在创建类的任何实例或访问任何静态成员之前,会自动调用静态构造函数。

Person 类包括三个实例构造函数:

无参数构造函数 public Person() 使用静态字段的值初始化 personNamepersonAge 字段 defaultNamedefaultAge。 这意味着,如果在创建 Person 对象时未提供任何参数,则 "unknown" 默认值用于名称和年龄。

构造函数 public Person(string name) 采用单个参数 name,并使用此值初始化 personName 字段。 personAge 字段使用静态字段 defaultAge的值进行初始化。 此构造函数允许在使用默认期限时创建具有指定名称的 Person 对象。

构造函数 public Person(string name, int age) 采用两个参数,nameage。 它用 personName 参数的值和具有 age 参数字符串表示形式的 name 字段初始化 personAge 字段。 此构造函数允许创建具有指定 Personnameage 对象。

静态构造函数的属性

静态构造函数具有以下属性:

  • 静态构造函数不采用访问修饰符或具有参数。

  • 类只能有一个静态构造函数。

  • 无法继承或重载静态构造函数。

  • 静态构造函数不能直接调用,只能由公共语言运行时(CLR)调用。 它会自动调用。

  • 用户在程序中执行静态构造函数时没有控制权。

  • 自动调用静态构造函数。 在创建第一个实例之前,它会初始化该类,或者引用该类(而不是其基类)中声明的任何静态成员。 静态构造函数在实例构造函数之前运行。 如果静态字段变量初始值设定项存在于静态构造函数的类中,则它们以在类声明中显示的文本顺序运行。 初始值设定项在静态构造函数之前立即运行。

  • 如果未提供用于初始化静态字段的静态构造函数,则所有静态字段都会初始化为其默认值。

  • 如果静态构造函数引发异常,运行时不会再次调用它,并且该类型在应用程序域的生存期内保持未初始化状态。 最常见的情况是,当静态构造函数无法实例化类型或静态构造函数中发生的未经处理的异常时,将引发 TypeInitializationException 异常。 对于未在源代码中显式定义的静态构造函数,故障排除可能需要检查中间语言 (IL) 代码。

  • 静态构造函数的存在可防止添加 BeforeFieldInit 类型属性。 这会限制运行时优化。

  • 声明为 static readonly 的字段只能作为声明的一部分或在静态构造函数中分配。 当不需要显式静态构造函数时,请在声明中初始化静态字段,而不是通过静态构造函数来更好地优化运行时。

  • 运行时在单个应用程序域中调用静态构造函数不超过一次。 该调用基于类的特定类型在锁定区域中进行。 静态构造函数正文中不需要额外的锁定机制。

注意

虽然无法直接访问,但应记录显式静态构造函数的存在,以帮助排查初始化异常问题。