.NET不可变对象使用最佳实践?我应该尽可能多地使用它们吗?

时间:2010-04-01 00:52:45

标签: c# object immutability

假设我有一个简单的对象,例如

class Something
{
   public int SomeInt { get; set; }
}

我已经读过使用不可变对象更快,更好的方法来使用业务对象?如果是这样的话,我是否应该努力制作所有的物品:

class ImmutableSomething
{
   public int SomeInt { get { return m_someInt; } }
   private int m_someInt = 0;

   public void ChangeSomeInt(int newValue)
   {
       m_someInt = newvalue;
   }
}

你觉得怎么样?

5 个答案:

答案 0 :(得分:7)

你描绘的不是一个不可改变的对象;简单地将set代码移动到专用的setter方法中不会使对象成为不可变的。根据定义,不可变对象无法更改,因此您可以更改任何对象属性或字段的值这一事实意味着它不是不可变的。

就“更快”或“更好”而言,不可变对象本质上不比可变对象更快或更“好”;除了你不能改变任何价值这一事实外,他们没有什么特别之处。

答案 1 :(得分:6)

正如其他人所说,你发布的内容并非一成不变。这是不可变对象的样子。 readonly关键字表示可以设置属性的支持字段的唯一位置在构造函数中。基本上,在构造对象之后永远是它。

public class ImmutableSomething
{
    private readonly int _someInt;
    public int SomeInt
    {
        get
        {
            return _someInt;
        }
    }

    public ImmutableSomething(int i)
    {
        _someInt = i;
    }

    public ImmutableSomething Add(int i){
        return new ImmutableSomething(_someInt + i);
    }
}

这在函数式编程中是一个大问题,因为不是谈论对象,而是谈论值。值永远不会改变,因此您知道当您将它们传递给函数或方法时,您的原始值将永远不会更改或更新。对于可变对象,您无法做到这一点。

使用不可变对象构建的代码可以轻松并行化,因为没有可写的共享状态。如果某个其他线程获得了您的价值并想要对其做某些事情,那么它就不能。以任何方式“改变”它会产生一个新的对象,在该对象的内存中有一个全新的位置。

因此,一旦你处理了值,你就可以做一些特殊的事情,比如实习,这就是.NET用字符串做的事情。因为“Hello World”是一个值并且永远不会改变,所以对“Hello World”的引用与其他引用一样好,所以不要在内存中的一百个插槽中使用“Hello World”,而是将它放在一个插槽中将所有对“Hello World”的引用设置为指向该一个槽。所以你在内存方面获得了巨大的胜利,但是你要付出性能损失,因为每当你创建一个新的字符串时,你必须检查实习池以确保它不存在。

答案 2 :(得分:3)

深度不可变对象的主要优点是可以很容易地获取其属性的“快照” - 只需复制对象的引用即可。无论对象有多大或多么复杂,只需复制一个引用就可以“快照”整个事物。

相比之下,如果想要获取可变对象属性的快照,则需要复制所有这些属性。如果这些属性中的任何一个本身都是可变对象,则有必要复制所有这些属性。在某些情况下,制作可变对象状态的可用副本可能非常复杂甚至是不可能的,因为对象的状态可能与单例的状态交织在一起。

虽然不可变对象比可变对象更容易“快照”,但是有时很难给出一个不可变对象,除了一些微小的改变之外,产生一个类似于第一个的实例。在这方面,可变对象有时可以更容易使用。有时,将数据从不可变对象复制到可变对象,更改它,然后生成一个保存已更改数据的新不可变对象会很有用。不幸的是,没有任何通用方法可以使用类自动化这种转换。但是,还有另一种选择。

具有暴露字段的结构,它们都是值原语或不可变类引用,可以提供一种非常方便的方法,用于将信息保存在不可变对象中,将其复制到可变形式,修改它,然后创建一个新的不可变对象。或者,可以通过仅复制结构本身来轻松地复制结构中的数据。复制包含一个或两个int大小字段的结构比复制不可变对象引用要贵一些,但复制结构并更改它通常比创建一个新的更改的不可变对象便宜得多。重要的是要注意,由于.net语言处理结构的一些怪癖,有些人认为可变结构是邪恶的。我建议一般来说,结构最好只是简单地暴露它们的字段,并避免使用任何变异this的方法(除了构造函数)。这将避免与可变结构相关的大多数怪癖,并且通常会提供比使用不可变类,可变类或所谓的不可变(真正“仅通过赋值”)结构所获得的更好的性能和更清晰的语义。

顺便提一下,设计一个不可变对象有时很有用,因此生成一个稍微不同的对象会将旧对象中的所有内容复制到一个新对象,但更改了相应的部分,有时设计一个不可变的对象很有用。对象,以便稍微不同的对象将能够“重用”大多数原始对象。在许多情况下,如果对象的读取频率比写入的频率高得多,那么前一种方法将更有效,并且在更改对象后不需要保留旧的快照。在人们希望保留许多快照的情况下,后一种方法可能更有效,并且相对于读取访问,更新频繁。

答案 3 :(得分:2)

这不是一个不可变的对象。这个的不可变版本就像是

class ImmutableSomething : ISomething
{
    public readonly int SomeInt;

    public ImmutableSomething(int i)
    {
        SomeInt = i;
    }

    public ImmutableSomething AddValue(int add)
    {
        return new ImmutableSomething(this.SomeInt + add);
    }
}

不可变对象的主要好处是对象本身永远不会改变,因此您不会冒险改变基础值的一部分代码,特别是在多线程情况下,但这通常适用。这些保证通常会使对象“更好”,因为你知道会发生什么,但没有什么可以使不可变的本身“比可变对象更快”。

例如,DateTimes是不可变的,所以你可以做像

这样的事情
DateTime someReferenceTime = DateTime.Now;

myBusinessLayer.DoABunchOfProcessingBasedOnTime(someReferenceTime);

// Here you are guaranteed that someReferenceTime has not changed, and you can do more with it.

相似
StringBuilder sb = new StringBuilder("Seed");

myBusinessLayer.DoStuffBasedOnStringBuilder(sb);

// You have no guarantees about what sb contains here.

答案 4 :(得分:0)

不考虑示例实际上不显示不可变对象的观点,不可变对象的主要好处是它们使某些多线程操作变得简单且无锁。例如,在多线程环境中无需锁定就可以枚举不可变树结构,而如果树是可变的,则需要引入锁以便安全地枚举它。

但是对于不可变对象来说,没有任何神奇之处可以使它们本身更快。