在一个关于C#模式验证最佳实践的问题中,得票最高的回答如下:
我倾向于在构造函数中执行所有验证。这是必须的,因为我几乎总是创建不可变对象。
在C#中如何精确地创建不可变对象?您只使用readonly
关键字吗?
如果您想在Entity Framework生成的模型类的构造函数中进行验证,该怎么做呢?
它看起来像下面这样吗?
public partial readonly Person
{
public Person()
}
在一个关于C#模式验证最佳实践的问题中,得票最高的回答如下:
我倾向于在构造函数中执行所有验证。这是必须的,因为我几乎总是创建不可变对象。
在C#中如何精确地创建不可变对象?您只使用readonly
关键字吗?
如果您想在Entity Framework生成的模型类的构造函数中进行验证,该怎么做呢?
它看起来像下面这样吗?
public partial readonly Person
{
public Person()
}
这里有一个来自评论的有趣问题:
什么样的对象不需要在某个时刻修改值呢?我猜测不是模型类,对吗?我曾经需要更改数据库中一个人的姓名 - 这不符合这个想法。
好的,考虑一下已经是不可变的事物。数字是不可变的。一旦你拥有数字12,它就是12了。你无法改变它。如果你有一个包含数字12的变量,你可以将变量的内容更改为13,但你改变的是变量而不是数字12。
字符串也一样。"abc" 就是 "abc",它永远不会改变。如果你有一个包含“abc”的变量,你可以将其更改为“abcd”,但这不会改变“abc”,而是改变了变量。
那列表怎么办呢?{12,“abc”} 是由12和“abc”组成的列表,该列表永远不会改变。列表 {12,“abcd”} 是一个不同的列表。
这就是事情开始变得混乱的地方。因为在C#中,你可以两种方式做到这一点。如果允许列表在不改变身份的情况下更改其内容,则可以说这两个列表之间具有引用标识。
当你谈论“模型”时,你说得很对。你正在建模一个会发生变化的东西吗?如果是的话,那么使用能够更改的类型来建模可能是明智的选择。其好处在于模型的特性与被建模的系统相匹配。缺点是实现“回滚”功能(即“撤销”更改)变得非常棘手。
也就是说,如果你把{12, "abc"}变成了{12, "abcd"},然后想要撤销这个操作,你该怎么做呢?如果这个列表是不可变的,你只需要保留两个值并选择哪一个值作为“当前”值。如果这个列表是可变的,那么你必须让撤销逻辑保留一个“撤销函数”,这个函数知道如何撤销这个变化。
针对你具体的例子,你当然可以创建一个不可变的数据库。那么如果你想要修改这个不可变数据库中某人的姓名,你该怎么做呢?你不能直接修改,而是要创建一个新的数据库,其中包含你想要的数据。不可变类型的技巧在于高效地进行操作,而不会复制大量字节。不可变数据结构的设计需要找到聪明的方法在两个几乎相同的结构之间共享状态。
将所有字段声明为只读是创建不可变对象的一个好步骤,但仅此还不足。这是因为只读字段仍然可以是对可变对象的引用。
在C#中,编译器不强制执行不可变性。你只需小心谨慎。
List<T>.AsReadOnly
。 - MasterMasticpublic class MyClass
{
private readonly string _myString;
public string MyString
{
get
{
return _myString;
}
}
public MyClass(string myString)
{
// do some validation here
_myString = myString;
}
}
public class MyClass
{
private string _myString;
public string MyString
{
get
{
return _myString;
}
private set
{
_myString = value;
}
}
public MyClass(string myString)
{
// do some validation here
_myString = myString;
}
// Not sure if you can change accessibility of constructor - I can try it later
public MyClass()
{}
}
C# 9将推出名为Record的新功能。如果您希望使单个属性不可变,则初始化只读属性非常有效。如果您希望整个对象是不可变的并且表现得像值一样,则应将其声明为记录:
public data class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
@Eric Lippert 很好的评论,但是除此之外,回答这个问题:
你会拥有什么样的对象,不需要在某个时刻修改其值?我猜不是一个模型类,对吗?我曾经不得不更改数据库中一个人的名字 - 这与这个想法不符。
假设你有一个大型数据结构,并且你想查询它的信息,但它一直在变化。你需要某种锁定系统来确保在有人从一个地方存放东西到另一个地方时,你不会尝试计算系统中的总数。(比如说仓库管理系统)
而这很难做到,因为这些事情总是以意想不到的方式影响着事物,数据在你脚下发生了变化。
如果您能够在不更新大型数据结构时将其冻结,以便不会更改任何内存并且在一致状态下暂停,那该多好?现在,当您再次想要更改它时,您必须将数据结构复制到新位置,而且它相当大,所以这是一个缺点,但好处是您无需锁定任何内容,因为数据的新副本未共享,直到它已被更新。这意味着任何人在任何时候都可以读取最新的数据结构副本,并进行复杂操作。
如果您讨厌处理并发问题并且没有太多数据要处理,那么这是非常有用的概念。(例如,如果数据大小为1MB,每秒更新10次,则需要复制10MB的数据)