什么是可变类。我们如何在C#中创建一个可变且不可变的类
在CMMI 5级公司访谈中,有人问我如何在C#中创建可变且不变的类。我听说过可变且不可变的,这意味着可以更改和不能更改,例如 String 和 StringBuilder 。
I was asked in a CMMI level 5 company interview about how to create a mutable and immutable class in C#. I have heard of mutable and immutable which means can change and cannot change, like String and StringBuilder.
但是,我不知道如何创建可变的类和不可变的类。我们有String和String构建器。但是当涉及到创建一个对象时,我被迫在网上搜索该对象,但找不到有用的东西,所以想到在这里询问。
But, I was not aware of how to create a mutable class and immutable class. We have String and String builder for this. But when it comes to creating one, I was forced to search the web for this, but couldn't find anything useful, so thought of asking here.
但是,我尝试通过在类及其Getter中定义一个属性来创建它,我创建了一个字符串新对象来复制它。但是未能成功理解。
However, I tried creating it by defining a property in the class and on its Getter, I created a new object of string to replicate it. But was not successful in understanding.
此外,我提到在stackoverflow中已经问过一个关于不变和可变的问题。但是,我的问题不同。我想知道是否要创建一个可变的类,那么除了使用String或其他可变类之外,我将如何处理它。
Also, I have referred that a question is already asked in stackoverflow about immutable and mutable. But, my question is different. I wanted to know if I wanted to create a mutable class then how will I go with it apart from using String or other mutable classes.
C#对 const
-的支持程度不同C ++提供的正确性(忽略代码合同),但它仍然提供了 readonly
修饰符(在C#6.0中为真正的只读自动属性),可以提供帮助。
C# does not have the same level of support for const
-correctness that C++ offers (ignoring Code Contracts), but it does still provide for the readonly
modifier (and true read-only auto-properties in C# 6.0) which helps.
C#还缺乏对Record类型的语法支持,不幸的是,这些记录类型是从C#7中提取的,因此我们不得不再等一年(更新:截至2018年中, C#8.0有望具有记录类型,但鉴于其非常长的新功能列表)。
C# also lacks syntactic support for Record types, which unfortunately were pulled from C# 7, so we'll have to wait another year for that (Update: As of mid-2018, C# 8.0 is expected to have Record types, but C# 8.0 probably won't be finally released until into 2020 given its very long list of new features).
无论如何,.NET中的不可变类型只是POCO 1 构造后无法修改其状态。请注意,只有在您的类型的每个字段都标记为 readonly
并且每个复杂(即非标量)成员也受到类似约束的情况下,编译器才会强制执行此操作。
Anyway, an immutable type in .NET is just a POCO1 which cannot have its state modified after construction. Note that this is only enforced by the compiler if your type has every field tagged as readonly
and that every complex (i.e. non-scalar) member is also similarly constrained.
如果您的类型具有任何数组成员,则该类型不能真正不变,因为C#中没有强制执行只读缓冲区(C ++确实如此)。这意味着在实践中,C#中的不可变类型只是精心设计的POCO,消费者(将按规则行事(例如,不作反思))可以就使用它时做出某些假设,例如不可变类型本质上是线程安全的。就是这样。运行时没有特殊的AOT或JIT优化,也没有任何特殊的行为。这是一种非常人为因素的东西。
If your type has any array members then the type cannot be truly immutable because there is no enforcement of read-only buffers in C# (C++ does). Which means in-practice that an "immutable" type in C# is just a well-designed POCO that a consumer (who would be playing by the rules (e.g. no reflection)) can make certain assumptions about when using it, for example, an immutable type is inherently thread-safe. But that's it. There are no special AOT or JIT optimisations nor any special behaviour exhibited by the runtime. It's a very "human factors"-kinda thing.
以下此类是不可变的:
class Immutable {
private readonly String foo;
public Immutable(String foo, String bar) {
this.foo = foo;
this.Bar = bar;
}
public String Bar { get: }
public String Baz { get { return this.foo.Substring( 0, 2 ); } }
}
这是不可变的,因为每个字段(即其实例状态)都是只读
和不可变(我们只知道这是因为众所周知, System.String
是不可变的)。如果将 foo
更改为 StringBuilder
或 XmlElement
,则它将
It is immutable because every field (i.e. its instance-state) is both readonly
and immutable (we only know this because System.String
is well known to be immutable). If foo
were changed to StringBuilder
or XmlElement
then it would no longer be immutable.
请注意,严格来说, readonly
修饰符对于不变性不是必需的,它只是使它更易于演示,并且确实增加了一定程度的编译时强制执行(并且可能一些运行时优化)。
Note that strictly speaking, the readonly
modifier is not necessary for immutability, it just makes it easier to demonstrate, and it does add some level of compile-time enforcement (and possibly some runtime optimisation).
为了比较起见,此类不是不可变的(即,它是可变的):
For comparison's sake, this class is not immutable (i.e. it is mutable):
class Mutable {
private readonly Int32[] values;
public Mutable(Int32 values) {
this.values = values;
}
public Int32[] GetValues() {
return this.values;
}
}
这是可变的,因为:
-
Int32 []
(数组类型)是可变的 - 它通过
GetValues
- 返回对可变数组的引用。在构造过程中,它接受可变对象参数。
-
Int32[]
(an array type) is mutable - It returns a reference to the mutable array via
GetValues
- It accepts a mutable object parameter during construction.
下面是一个说明其不可变的示例:
Here's an example demonstrating why it's not immutable:
Int32[] values = { 0, 1, 2, 3 };
Mutable mutable = new Mutable( values );
Print( mutable.GetValues() ); // prints "0, 1, 2, 3"
values[0] = 5;
Print( mutable.GetValues() ); // prints "5, 1, 2, 3"
如果 Mutable
是不可变的,因此在使用 Mutable
的API时,对值
的后续更改将不可见:第二个 Print
调用将显示与第一个相同的输出。
If Mutable
were immutable then subsequent changes to values
would not be visible when using Mutable
's API: the second call to Print
would display the same output as the first.
但是,您可以使用不可变的类型即使您使用的是数组或复杂类型,也可以通过隐藏所有修改状态的方法来完成。例如,返回 ReadOnlyCollection< Int32>
而不是 Int32 []
,并始终执行所有复杂内容的深层复制/克隆以及传入的可变值。但是编译器,JIT和运行时仍然不够成熟,无法确定这使对象类型变得不可变-因此,为什么必须记录它并信任使用方可以正确使用它(或者,如果您是使用方,则可以信任上游开发人员)他们正确地实现了它)
However, you can have an immutable type even if you are using arrays or complex types: and this is done by that hiding all the ways to modify state. For example, return ReadOnlyCollection<Int32>
instead of Int32[]
and always perform a deep copy/clone of all complex and mutable values passed-in. But the compiler, JIT, and runtime are still not sophisticated enough to determine this renders the object type immutable - hence why you have to document it and trust your consumer to use it properly (or if you're a consumer, trust your upstream dev that they implemented it correctly)
下面是一个包含数组的不可变类型的示例:
Here's an example of an immutable type that contains an array:
class Immutable {
private readonly Int32[] values;
public Mutable(Int32 values) {
if( values == null ) throw new ArgumentNullException(nameof(values));
this.values = (Int32[])values.Clone();
}
public IReadOnlyList<Int32> GetValues() {
return this.values;
}
}
*。在构造期间,输入数组是浅复制的(使用 Array.Clone()
)-因此,将来传递给构造函数的对象的任何更改都不会影响任何 Immutable
类实例。
*如果 values
数组包含不可改变的非标量值,则构造函数将必须对元素执行深度复制以确保其值将与其他任何将来的更改隔离开来。
* 值
数组永远不会直接暴露给消费者。
*。 GetValues()
返回内部数组的 IReadOnlyList< T>
view (这是.NET 4.5中的新功能)。与返回 ReadOnlyCollection< T>
包装器(在.NET 2.0中引入)相比,此方法更轻巧。
*. The input array is shallow-copied (using Array.Clone()
) during construction - so any future changes to the object passed into the constructor won't affect any Immutable
class instances.
* If the values
array contained non-immutable, non-scalar values then the constructor would have to perform a "deep-copy" of the elements to ensure its values would be isolated from any future changes elsewhere.
* The values
array is never directly exposed to consumers.
*. GetValues()
returns an IReadOnlyList<T>
view of the internal array (this is new in .NET 4.5). This is more lightweight than returning a ReadOnlyCollection<T>
wrapper (introduced in .NET 2.0).
1 :POCO是普通旧CLR对象类型,在实践中表示任何类
或 struct
而不要求它继承某些父超类型或实现任何特定接口。该术语通常用于引用ORM库,例如Linq-to-SQL,Entity Framework或NHibernate,它们(在其早期版本中)要求每个实体类都派生自某些基本实体类型,或采用某些技术(例如 INotifyPropertyChanged
)。有关更多详细信息,请参见此处:实体框架中的POCO是什么?
1: A POCO is a "Plain Old CLR Object" type, which in-practice means any class
or struct
without any requirements that it inherit some parent supertype or implement any particular interface. The term is often used in reference to ORM libraries like Linq-to-SQL, Entity Framework or NHibernate which (in their earlier versions) required each entity class to derive from some base entity type, or employ certain techniques (e.g. INotifyPropertyChanged
). See here for more details: What is POCO in Entity Framework?