C# 9.0 引入了一种称为 init 的新型属性访问器。
init only 属性只能在对象创建期间分配,不能进一步更改。这强制实施了不可变性。
让我们看看如何定义 init only 属性:
public record Product
{
public int Id { get; init; }
public string Name { get; init; }
public decimal Price { get; init; }
}
// Create an instance of Product's record
var product = new Product
{
Id = 1,
Name = "Phone",
Price = 500.00m
};
创建记录时的最佳做法是仅将其所有属性声明为 init。这样可以确保属性无法更改,从而确保记录数据的不变性。尽管您也可以在类中定义 init 属性。
仅 init 属性具有与 set 属性相同的行为。如果需要,您可以为属性使用支持字段:
public record Product
{
private readonly decimal _price;
public required decimal Price
{
get => _price;
init => _price = value;
}
// Other properties...
}
C# 11 中引入了一个名为 required 的新关键字,这是对 init only 属性的一个很好的补充:
public record Product
{
public required int Id { get; init; }
public required string Name { get; init; }
public required decimal Price { get; init; }
}
这确保了在创建对象时应分配所有标有 required 关键字的属性,否则会引发编译错误:
// This code doesn't compile as Price property is not assigned
var product = new Product
{
Id = 1,
Name = "Phone"
};
init 和 required 关键字是一对很好的搭档。Required 确保在创建异议期间仅分配 init 属性,因为这些属性无法进一步更改。
C# 还提供了一种简化的记录声明形式:
public record Product(int Id, string Name, decimal Price);
var product = new Product(1, "PC", 1000.00m);
只需一行代码!我们使用记录的构造函数分配所有属性,该构造函数称为主构造函数。在后台,此代码被转换为记录声明的经典形式,所有属性仅是 init。这种形式的记录也称为位置记录,因为引擎盖下的所有属性都是按照与它们在主构造函数中的位置完全相同的顺序创建的。当记录没有很多字段时,声明的位置形式看起来非常优雅和简洁。
使用主构造函数创建记录的对象时,您必须分配所有属性。尽管此处未使用必需的关键字。
在运行时创建对象时,required 关键字不起作用,即使未为字段分配值,它也不会引发异常。required 仅在编译时起作用。尽管有一个例外:在 NET 8 中使用序列化和反序列化的对象在未为必需属性分配值时可能会引发异常。System.Text.Json
根据需要标记可为 null 的属性是完全可以的。它强制您在创建对象时为其分配一个实际值或 null。让我们向记录中添加一个可以为 null 的属性:DescriptionProduct
public record Product
{
public required int Id { get; init; }
public required string Name { get; init; }
public required string? Description { get; init; }
public required decimal Price { get; init; }
}
var product = new Product
{
Id = 1,
Name = "PC",
Price = 1000.00m,
Description = "Some amazing PC" // Assign some value
};
var product2 = new Product
{
Id = 1,
Name = "PC",
Price = 1000.00m,
Description = null // Explicitly assign null here
};
在适当的时候,您确实应该考虑对所有类型的属性使用 required 关键字。
让我们看看下面的代码,在其中我们创建了一个具有必需属性和构造函数的类:
public class User
{
public User(string name, int age)
{
Name = name;
Age = age;
}
public required string Name { get; init; }
public required int Age { get; init; }
}
var user = new User("Anton", 30); // This doesn't compile
此代码不会编译,因为编译器会告知未分配所需的属性,即使它们是从构造函数分配的。要修复代码,您需要添加属性赋值以及构造函数:
var user = new User("Anton", 30)
{
Name = "Anton",
Age = 30
}; // Now this compiles
这是一个真正的警告,在对类进行建模时,您应该考虑这种奇怪的行为。