C#语言中的主要构造函数

99a3490520134b55fb97ad1f2cbedac3.png

主构造函数是 C# 12 中的一项新功能,可用于直接在构造函数参数列表中定义和初始化属性。此功能消除了对重复代码的需要,并使代码更加简洁和可读。在这篇博文中,我将解释如何使用主构造函数以及它们提供的好处。

什么是主构造函数?

主构造函数是一种简洁的语法,用于声明一个构造函数,其参数在类型的主体中的任何位置都可用。例如,您可以使用主构造函数定义一个类,如下所示:

public class Person(string name, int age)  
{  
    public string name { get; set; }  
    public int age { get; set; }  
}

主构造函数的参数在整个类定义的范围内,因此可以使用它们来初始化属性、字段或其他成员。但是,默认情况下,它们不会存储为字段或属性,除非您显式将它们分配给一个字段或属性。它们也不能作为 或 访问,因为它们不是类的成员。

如何使用主构造函数?

主构造函数参数的最常见用途是:

  • 初始化成员字段或属性。

  • 作为构造函数调用的参数。base()

  • 在实例成员中引用构造函数参数。

让我们看一些如何在不同场景中使用主构造函数的示例。

初始化属性

主构造函数的主要优点之一是它们简化了类中属性的初始化,特别是对于具有许多属性的类。例如,可以使用如下主构造函数定义具有四个只读属性的类:

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

  • 主构造函数参数是可变的。它们可以分配给类型的主体或在类型的主体中进行修改,这可能会影响类型的不变性和线程安全性。如果要确保它们是只读的,则必须使用关键字或将它们声明为字段。initreadonly

  • 类或结构的所有其他构造函数都必须通过构造函数调用直接或间接调用主构造函数。这可确保始终分配主要构造函数参数,但也限制了构造函数重载的灵活性和表现力。this()

下面是一个使用主构造函数的类的示例,并演示了其中一些限制:

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}!");  
    }  
}

如果你喜欢我的文章,请给我一个赞!谢谢

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值