如何在 C# 中创建不可变对象?
在有关 C# 模式验证最佳实践的问题中,得票最高的答案 说:
我倾向于在构造函数中执行所有验证。这是必须的,因为我几乎总是创建不可变的对象。
究竟如何在 C# 中创建不可变对象?您只使用readonly
关键字吗?
如果您想在实体框架生成的模型类的构造函数中进行验证,那么这到底是如何工作的?
会像下面这样吗?
public partial readonly Person
{
public Person()
}
In a question about Best practices for C# pattern validation, the highest voted answer
says:
I tend to perform all of my validation in the constructor. This is a must because I almost always create immutable objects.
How exactly do you create an immutable object in C#? Do you just use the readonly
keyword?
How exactly would this work if you want to validate in the constructor of your Entity Framework generated model class?
Would it look like below?
public partial readonly Person
{
public Person()
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
@Eric Lippert 很好的评论,但除此之外还回答了问题:
假设您有一个大型数据结构,并且您想要查询其信息,但它一直在变化。您需要某种锁定系统来确保当有人将某些东西从一个地方存入另一个地方时,您不会说“尝试并计算系统中的总数”。 (比如仓库管理系统)
这很难做到,因为这些事情总是以意想不到的方式影响事物,数据在你脚下发生变化。
如果您可以在不更新大型数据结构时冻结它,这样就无法更改内存并且它会暂停在一致状态,该怎么办?现在,当您想再次更改它时,您必须将数据结构复制到新位置,而且它相当大,所以这是一个缺点,但好处是您不必锁定任何内容,因为数据的新副本不再共享直到它被更新。这意味着任何人在任何时候都可以读取数据结构的最新副本,执行复杂的操作。
所以,如果您讨厌处理并发问题并且没有太多数据需要处理,那么这是非常有用的概念。 (例如,如果 1MB 数据并更新 10/秒,则复制 10MB 数据)
@Eric Lippert Good comment, but in addition in answer to the question:
Let's say you have a large datastructure and you want to query its information, but it's changing all the time. You need some kind of locking system to make sure that you don't say try and count the total in the system while somebody is depositing something from one place to another. (Say a warehouse management system)
And that's hard to do because these things always affect things in unexpected ways, the data changing under your feet.
What if you could freeze your large datastructure when you're not updating it, so that no memory can be altered and it is paused at a consistent state? Now when you want to change it again you have to copy the datastructure to a new place, and it's fairly large, so that's a downside, but the upside is you won't have to lock anything because the new copy of the data goes unshared until it has been updated. This means anyone at any point can read the latest copy of the datastructure, doing complex things.
So yep very useful concept if you hate dealing with concurrency issues and don't have too much data to deal with. (E.g. if 1MB of data and updating 10/sec that's 10MB of data being copied)
这里有趣的问题是评论中的问题:
好吧,考虑一下已经不可变的事情。数字是一成不变的。一旦你有了数字12,它就是12。你无法改变它。如果您有一个包含 12 的变量,您可以将该变量的内容更改为 13,但您更改的是变量,而不是数字 12。
与字符串相同。 “abc”就是“abc”,它永远不会改变。如果您有一个包含“abc”的变量,您可以将其更改为“abcd”,但这不会更改“abc”,而是更改变量。
清单怎么样? {12, "abc"} 是 12 后跟“abc”的列表,并且该列表永远不会改变。列表 {12, "abcd"} 是一个不同的列表。
这就是事情脱轨的地方。因为在 C# 中你可以用任何一种方法来做。如果允许列表在不改变其身份的情况下改变其内容,则可以说这两个列表之间存在引用身份。
当你谈论“模型”时,你说得一针见血。您正在建模一些会发生变化的东西吗?如果是这样,那么使用变化的类型对其进行建模可能是明智的。这样做的好处是模型的特征与所建模的系统相匹配。不利的一面是,执行“回滚”功能(即“撤消”更改)之类的操作变得非常棘手。
也就是说,如果您将 {12, "abc"} 突变为 {12, "abcd"},然后想要回滚该突变,该怎么做?如果列表是不可变的,您只需保留这两个值并选择您想要的“当前”值。如果列表是可变的,那么您必须让撤消逻辑保留在“撤消函数”周围,该函数知道如何撤消突变。
至于您的具体示例,您当然可以创建一个不可变的数据库。如何更改不可变数据库中某人的姓名?你不知道。您创建一个新数据库,其中包含您想要的数据。不可变类型的技巧是高效地做到这一点,而无需复制数十亿字节。不可变的数据结构设计需要找到巧妙的方法来在两个几乎相同的结构之间共享状态。
The interesting question here is your question from the comments:
Well, consider things that are already immutable. Numbers are immutable. Once you have the number 12, it's 12. You can't change it. If you have a variable that contains 12, you can change the contents of the variable to 13, but you are changing the variable, not the number 12.
Same with strings. "abc" is "abc", and it never changes. If you have a variable that contains "abc", you can change it to "abcd", but that doesn't change "abc", that changes the variable.
What about a list? {12, "abc"} is the list that is 12 followed by "abc", and that list never changes. The list {12, "abcd"} is a different list.
And that's where things go off the rails. Because in C# you can do it either way. You can say that there is referential identity between those two lists if lists are allowed to mutate their contents without changing their identity.
You hit the nail right on the head when you talk about the "model". Are you modeling something that changes? If so, then it is possibly wise to model it with a type that changes. The benefit of that is that the characteristics of the model match the system being modeled. The down side is that it becomes very tricky to do something like a "rollback" functionality, where you "undo" a change.
That is, if you mutate {12, "abc"} to {12, "abcd"} and then want to roll back the mutation, how do you do it? If the list is immutable you just keep around both values and choose which one you want to be the "current" value. If the list is mutable then you have to have the undo logic keep around an "undo function" which knows how to undo the mutation.
As for your specific example, you certainly can create an immutable database. How do you change the name of someone in your immutable database? You don't. You create a new database that has the data you want in it. The trick with immutable types is to do so efficiently, without copying billions of bytes. Immutable data structure design requires finding clever ways to share state between two nearly-identical structures.
将所有字段声明为只读是创建不可变对象的一个很好的步骤,但仅此还不够。这是因为只读字段仍然可以是对可变对象的引用。
在 C# 中,编译器不强制执行不变性。你只需要小心。
Declaring all fields readonly is a good step towards creating an immutable object, but this alone is not sufficient. This is because a readonly field can still be a reference to a mutable object.
In C# immutability is not enforced by the compiler. You just have to be careful.
这个问题有两个方面:
第一个方面需要这样的结构:
现在的问题 - EF。 EF 需要无参数构造函数,并且 EF 必须具有属性设置器。我在这里问了非常类似的问题。
您的类型必须如下所示:
您还必须通知 EF 有关 MyString 属性的私有设置器 - 这是在 EDMX 文件中实体的属性中配置的。显然,当 EF 从 DB 中具体化对象时,不会进行验证。此外,您将无法使用 ObjectContext.CreateObject 等方法(您将无法填充对象)。
实体对象 T4 模板和默认代码生成创建工厂方法 CreateMyClass,而不是带参数的构造函数。 POCO T4模板不生成工厂方法。
我没有首先尝试使用 EF 代码。
This question has two aspects:
The first aspect demands sturcture like this:
Now the problem - EF. EF requires parameterless constructor and EF must have setters on properties. I asked very similar question here.
Your type must look like:
You must also inform EF about private setter of MyString property - this is configured in properties of enitity in EDMX file. Obviously there will be no validation when EF will materialize objects from DB. Also you will not be able to use methods like ObjectContext.CreateObject (you will not be able to fill the object).
Entity Object T4 template and default code generation create factory method CreateMyClass instead of constructor with paremeters. POCO T4 template doesn't generate factory method.
I didn't try this with EF Code first.
不可变值对象是不能更改的值对象。你不能修改它的状态,你必须创建新的状态
查看 Eric Lippert 的博客:
Kinds of Immutability
https: //learn.microsoft.com/en-us/archive/blogs/ericlippert/immutability-in-c-part-one-kinds-of-immutability
看看
C# 中的不可变对象模式 - 您怎么看?
An immutable value object is a value object that cannot be changed. You cannot modify its state, you have to create new ones
Check out Eric Lippert's blog:
Kinds of Immutability
https://learn.microsoft.com/en-us/archive/blogs/ericlippert/immutability-in-c-part-one-kinds-of-immutability
Have a look at
Immutable object pattern in C# - what do you think?
它在这种情况下不起作用,因为 EF 要求实体类的属性是公共的,否则它无法实例化它。
但欢迎您在代码中进一步使用不可变对象。
It wouldn't work in this context because EF requires the properties of the entity class be public otherwise it can't instantiate it.
But you're welcome to use immutable objects further in your code.
C# 9 提出了新的功能名称:Record。如果您想让单个属性不可变,则仅限 Init 的属性非常有用。如果您希望整个对象不可变并且表现得像一个值,那么您应该考虑将其声明为记录:
类声明中的 data 关键字将其标记为记录。
参考: https:// devblogs.microsoft.com/dotnet/welcome-to-c-9-0/#records
C# 9 is coming up with new feature names as Record. Init-only properties are great if you want to make individual properties immutable. If you want the whole object to be immutable and behave like a value, then you should consider declaring it as a record:
The data keyword on the class declaration marks it as a record.
Reference: https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/#records