在CMMI 5级公司访谈中,有人问我如何在C#中创建可变且不变的类。我听说过可变和不可变的意思是可以更改和不能更改,例如String和StringBuilder。
但是,我不知道如何创建一个可变的类和不可变的类。我们为此提供了String和String构建器。但是当涉及到创建一个对象时,我被迫在网上搜索该对象,但是找不到有用的东西,因此想到了这里。
但是,我尝试通过在类及其Getter上定义一个属性来创建它,我创建了一个字符串新对象来复制它。但未能成功理解。
另外,我提到在stackoverflow中已经问过一个关于不可变和可变的问题。但是,我的问题是不同的。我想知道是否要创建一个可变的类,那么除了使用String或其他可变类之外,我将如何处理它。
截至2020年末,用于.NET 5.0的C#9.0已发布,支持不可变的记录类型(支持复制构造函数,并且with
操作员可以轻松创建具有新属性值的新实例)。
C#对const
C ++正确性的支持程度不如C ++(忽略代码契约),但它仍提供有助于使用的readonly
修饰符(以及C#6.0中的真正只读自动属性)。
C#还缺乏对Record类型的语法支持,不幸的是,C#是从C#7中提取的,因此我们不得不再等一年(更新:截至2018年中,C#8.0有望具有Record类型,但C#8.0可能鉴于其非常长的新功能列表,该功能要到2020年才能最终发布)。
无论如何,.NET中的不可变类型只是POCO 1,无法在构造后修改其状态。请注意,只有在您的类型将每个字段都标记为readonly
且每个复杂(即非标量)成员也受到类似约束的情况下,编译器才会强制执行此操作。
如果您的类型具有任何数组成员,则该类型不能真正不可变,因为C#中没有强制执行只读缓冲区(C ++确实如此)。这意味着在实践中,C#中的“不可变”类型只是精心设计的POCO,消费者(将按规则行事(例如,不作反思))可以对使用它时进行某些假设,例如不可变类型本质上是线程安全的。就是这样。运行时没有特殊的AOT或JIT优化,也没有任何特殊的行为。这是一种非常“人为因素”的东西。
下面的此类是不可变的:
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 ); } }
}
它是不可变的,因为每个字段(即其实例状态)都是readonly
并且都是不可变的(我们只知道这是因为System.String
众所周知,它是不可变的)。如果将foo
其更改为StringBuilder
或,XmlElement
则它将不再是不变的。
请注意,严格来说,readonly
修饰符对于不变性不是必需的,它只是使它易于演示,并且确实增加了一定程度的编译时强制执行(可能还对运行时进行了优化)。
为了进行比较,该类不是不可变的(即,它是可变的):
class Mutable {
private readonly Int32[] values;
public Mutable(Int32 values) {
this.values = values;
}
public Int32[] GetValues() {
return this.values;
}
}
这是可变的,因为:
Int32[]
(数组类型)是可变的GetValues
这是一个说明为什么它不是不变的示例:
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
是不可变的,则values
使用Mutable
的API时,对的后续更改将是不可见的:对的第二次调用Print
将显示与第一次相同的输出。
但是,即使使用数组或复杂类型,也可以具有不可变的类型:这是通过隐藏所有修改状态的方法来实现的。例如,使用returnReadOnlyCollection<Int32>
代替,Int32[]
并始终对传入的所有复杂和可变值执行深层复制/克隆。但是编译器,JIT和运行时仍然不够成熟,无法确定这使对象类型变得不可变-因此,为什么必须记录它并信任使用者可以正确使用它(或者,如果您是使用者,则可以信任上游开发人员他们正确地实施了它)
这是一个包含数组的不可变类型的示例:
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
数组包含不可更改的非标量值,则构造函数将必须对元素执行“深度复制”,以确保其值与将来其他位置的更改隔离开来。values
阵列永远不会直接暴露给消费者。GetValues()
返回内部数组的IReadOnlyList<T>
视图(.NET 4.5中的新增功能)。这比返回ReadOnlyCollection<T>
包装器(.NET 2.0中引入)更轻巧。1:POCO是“普通的旧CLR对象”类型,在实践中表示有任何class
或struct
没有要求它继承某些父超类型或实现任何特定接口。该术语通常用于引用诸如Linq-to-SQL,Entity Framework或NHibernate之类的ORM库,这些库(在其早期版本中)要求每个实体类都派生自某种基本实体类型,或采用某些技术(例如INotifyPropertyChanged
)。有关更多详细信息,请参见此处:Entity Framework中的POCO是什么?
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句