我在CMMI 5级公司的采访中被问到如何在C#中创建一个可变且不可变的类。我听说过mutable和immutable意味着可以改变而无法改变,比如 String 和 StringBuilder 。
但是,我不知道如何创建一个可变类和不可变类。我们有String和String构建器。但是当谈到创建一个时,我被迫在网上搜索这个,但找不到任何有用的东西,所以想到这里问。
但是,我尝试通过在类中定义属性并在其Getter上创建它,我创建了一个新的字符串对象来复制它。但是没有成功的理解。
另外,我已经提到在stackoverflow中已经提出了一个关于不可变和可变的问题。但是,我的问题是不同的。我想知道我是否想要创建一个可变类,那么除了使用String或其他可变类之外,我将如何使用它。
答案 0 :(得分:4)
C#对const
没有相同级别的支持--C ++提供的正确性(忽略代码约定),但它仍然提供readonly
修饰符(和真正的只读自动 - C#6.0中的属性有帮助。
C#也缺乏对Record类型的语法支持,遗憾的是它们是从C#7中提取的,因此我们将不得不再等一年(更新:截至2018年中期,C#8.0预计会有Record类型,但是在给定its very long list of new features)到2020年之前,C#8.0可能不会最终发布。
无论如何,.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
的API时,Mutable
的后续更改将不可见:第二次调用Print
将显示相同的输出第一个。
但是,即使使用数组或复杂类型,也可以使用不可变类型:这是通过隐藏修改状态的所有方法来完成的。例如,返回ReadOnlyCollection<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
没有任何要求继承某些父超类型或实现任何特定的接口。该术语通常用于引用ORM库,如Linq-to-SQL,Entity Framework或NHibernate(在其早期版本中)要求每个实体类从某种基本实体类型派生,或采用某些技术(例如{{1} })。有关详情,请参阅此处:What is POCO in Entity Framework?