主构造函数是 C# 12 中的一项新功能,可用于直接在构造函数参数列表中定义和初始化属性。此功能消除了对重复代码的需要,并使代码更加简洁和可读。在这篇博文中,我将解释如何使用主构造函数以及它们提供的好处。
主构造函数是一种简洁的语法,用于声明一个构造函数,其参数在类型的主体中的任何位置都可用。例如,您可以使用主构造函数定义一个类,如下所示:
public class Person(string name, int age)
{
public string name { get; set; }
public int age { get; set; }
}
主构造函数的参数在整个类定义的范围内,因此可以使用它们来初始化属性、字段或其他成员。但是,默认情况下,它们不会存储为字段或属性,除非您显式将它们分配给一个字段或属性。它们也不能作为 或 访问,因为它们不是类的成员。
主构造函数参数的最常见用途是:
让我们看一些如何在不同场景中使用主构造函数的示例。
主构造函数的主要优点之一是它们简化了类中属性的初始化,特别是对于具有许多属性的类。例如,可以使用如下主构造函数定义具有四个只读属性的类:
public class Employee(string firstName, string lastName, DateTime hireDate, decimal salary)
{
public string FirstName { get; init; } = firstName;
public string LastName { get; init; } = lastName;
public DateTime HireDate { get; init; } = hireDate;
public decimal Salary { get; init; } = salary;
}
此代码等效于以下没有主构造函数的代码:
public class Employee
{
public string FirstName { get; init; }
public string LastName { get; init; }
public DateTime HireDate { get; init; }
public decimal Salary { get; init; }
public Employee(string firstName, string lastName, DateTime hireDate, decimal salary)
{
FirstName = firstName;
LastName = lastName;
HireDate = hireDate;
Salary = salary;
}
}
如您所见,使用主构造函数可以减少初始化属性所需的样板代码量。它还使代码更具可读性,并降低了因缺少或错误排序的初始化语句而导致错误的风险。
若要使用主构造函数创建新对象,可以使用以下语法,该语法类似于传统方法:
var employee = new Employee("John", "Doe", new DateTime(2020, 1, 1), 50000);
该对象是使用 、 、 和 参数创建的,这些参数用于初始化相应的属性。
请注意,该类没有无参数构造函数,因此,如果尝试使用默认构造函数创建对象,则会收到编译器错误。如果要提供无参数构造函数,可以通过显式添加一个构造函数来实现:
public class Employee(string firstName, string lastName, DateTime hireDate, decimal salary)
{
public string FirstName { get; init; } = firstName;
public string LastName { get; init; } = lastName;
public DateTime HireDate { get; init; } = hireDate;
public decimal Salary { get; init; } = salary;
public Employee() : this("", "", DateTime.MinValue, 0) // Call the primary constructor with default values
{
}
}
主构造函数的另一个用例是调用带有参数的基构造函数。例如,假设您有一个具有主构造函数的基类和一个继承自其主构造函数并具有其主构造函数的派生类。可以使用该语法将派生类的主构造函数参数传递给基类构造函数。例如:PersonStudentPersonbase()
public class Person(string name, int age)
{
public string Name { get; init; } = name;
public int Age { get; init; } = age;
}
public class Student(string name, int age, string major) : base(name, age) // Call the base constructor with name and age
{
public string Major { get; init; } = major;
}
此代码等效于以下不带主构造函数的代码:
public class Person
{
public string Name { get; init; }
public int Age { get; init; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
public class Student : Person
{
public string Major { get; init; }
public Student(string name, int age, string major) : base(name, age) // Call the base constructor with name and age
{
Major = major;
}
}
使用主构造函数使代码更加简洁,并避免重复基本构造函数的参数。
若要使用主构造函数创建新对象,可以使用以下语法:
var student = new Student("Alice", 20, "Computer Science");
对象是使用 、 和 参数创建的,这些参数用于初始化 和 类的属性。
主构造函数的另一个好处是,可以在类的任何成员(如方法、属性、字段或嵌套类型)中引用构造函数参数。例如,可以使用一个主构造函数和两个从构造函数参数计算的只读属性来定义一个结构:
public readonly struct Distance(double dx, double dy)
{
public readonly double Magnitude { get; } = Math.Sqrt(dx \* dx + dy \* dy);
public readonly double Direction { get; } = Math.Atan2(dy, dx);
}
字段初始值设定项 和 使用主构造函数参数 和 。主构造函数参数不会在结构中的其他任何位置使用,因此它们不会存储为字段或属性。
前面的结构等效于以下没有主构造函数的代码:
public readonly struct Distance
{
public readonly double Magnitude { get; }
public readonly double Direction { get; }
public Distance(double dx, double dy)
{
Magnitude = Math.Sqrt(dx * dx + dy * dy);
Direction = Math.Atan2(dy, dx);
}
}
有时,您可能希望自定义主构造函数的行为或由其初始化的属性。例如,您可能希望在初始化之前或之后添加一些验证逻辑、执行一些计算或调用某些方法。为此,可以使用以下技术:
public class Employee(string firstName, string lastName, DateTime hireDate, decimal salary)
{
public string FirstName { get; init; } = firstName;
public string LastName { get; init; } = lastName;
public DateTime HireDate { get; init; } = hireDate;
private decimal salary;
public decimal Salary
{
get => salary;
init
{
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value), "Salary cannot be negative");
salary = value;
}
}
}
此代码定义一个私有字段和一个具有自定义访问器的公共属性。访问器检查传递给主构造函数的值是否有效,如果无效,则引发异常。否则,它会将值分配给字段。
主构造函数是一项功能强大且方便的功能,但它们也有一些您应该注意的限制。
以下是主要构造函数的一些限制:
public class Employee(string firstName, string lastName, DateTime hireDate, decimal salary)
{
public string FirstName { get; init; } = firstName;
public string LastName { get; init; } = lastName;
public DateTime HireDate { get; init; } = hireDate;
public decimal Salary { get; init; } = salary;
public void PrintInfo()
{
Console.WriteLine($"Name: {this.firstName} {this.lastName}"); // This is invalid, because firstName and lastName are not fields
Console.WriteLine($"Hire date: {this.HireDate}");
Console.WriteLine($"Salary: {this.Salary}");
}
}
此代码将导致编译器错误,因为 和 不是有效的表达式。this.firstNamethis.lastName
下面是一个使用主构造函数的类的示例,并演示了其中一些限制:
public class Person(string name, int age)
{
// name and age are not properties, they are parameters
// they can be assigned to or modified in the class body
public string Name { get; init; } = name;
public int Age { get; init; } = age;
// this constructor must call the primary constructor, otherwise will throw an error
public Person(string name) : this(name, 0)
{
// name is not accessible as this.name
Console.WriteLine($"Hello, {name}!");
}
}