C#中的不可变对象模式--你怎么看?[英] Immutable object pattern in C# - what do you think?

本文是小编为大家收集整理的关于C#中的不可变对象模式--你怎么看?的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

我在一些项目的过程中开发了一种模式,以创建不变的(可读)对象和不变的对象图.不变的物体具有100%螺纹安全的好处,因此可以跨线重复使用.在我的工作中,我经常在Web应用程序中使用此模式,以用于在内存中加载和缓存的配置设置和其他对象.缓存的对象应该始终是不变的,因为您想保证它们不会出乎意料地改变.

现在,您当然可以像以下示例一样轻松地设计不变的对象:

public class SampleElement
{
  private Guid id;
  private string name;

  public SampleElement(Guid id, string name)
  {
    this.id = id;
    this.name = name;
  }

  public Guid Id
  {
    get { return id; }
  }

  public string Name
  {
    get { return name; }
  }
}

这对于简单类来说是可以的 - 但是对于更复杂的类,我不喜欢通过构造函数传递所有值的概念.在属性上设置固定器是更可取的,并且构建新对象的代码更容易阅读.

那么如何使用setter创建不变的对象?

好吧,在我的图案对象中,直到您用单个方法调用将它们冻结为止.一旦冻结一个物体,它将永远永久地变为不变 - 它不能再次变成可变的物体.如果您需要对象的可变版本,则只需克隆它.

好的,现在使用一些代码.我在以下代码段中试图将图案煮沸至最简单的形式. IELENT是所有不变对象最终必须实现的基本接口.

public interface IElement : ICloneable
{
  bool IsReadOnly { get; }
  void MakeReadOnly();
}

元素类是IElement接口的默认实现:

public abstract class Element : IElement
{
  private bool immutable;

  public bool IsReadOnly
  {
    get { return immutable; }
  }

  public virtual void MakeReadOnly()
  {
    immutable = true;
  }

  protected virtual void FailIfImmutable()
  {
    if (immutable) throw new ImmutableElementException(this);
  }

  ...
}

让我们重构上面的样本元素以实现不变的对象模式:

public class SampleElement : Element
{
  private Guid id;
  private string name;

  public SampleElement() {}

  public Guid Id
  {
    get 
    { 
      return id; 
    }
    set
    {
      FailIfImmutable();
      id = value;
    }
  }

  public string Name
  {
    get 
    { 
      return name; 
    }
    set
    {
      FailIfImmutable();
      name = value;
    }
  }
}

您现在可以更改ID属性和名称属性,只要对象未通过调用MakerEadonly()方法标记为不可变的属性即可.一旦变成不可变的,呼叫设置器将产生一个不可分割的ElementException.

最终注意: 完整图案比此处显示的代码段更复杂.它还包含对不变对象的集合和不变对象图的完整对象图的支持.完整的模式使您可以通过在最外面对象上调用Makereadonly()方法来将整个对象图变为不可变.一旦您开始使用此模式创建较大的对象模型,泄漏对象的风险就会增加.泄漏的对象是一个对象,在更改对象之前未能调用FailiFimMutable()方法.为了测试泄漏,我还开发了一个通用泄漏检测器类,用于单位测试.它使用反射来测试所有属性和方法是否在不可变的状态下抛出了不可变化的ElementException. 换句话说,这里使用了TDD.

我已经非常喜欢这种模式,并在其中找到了巨大的好处.那么,我想知道的是,如果有人使用类似的模式?如果是,您知道有任何良好的资源可以记录下来吗?我本质上是在寻找潜在的改进以及该主题上可能已经存在的任何标准.

推荐答案

有关信息,第二种方法称为"冰棒不变性".

eric lippert有一系列有关不可变性的博客条目在这里.我仍然可以掌握CTP(C#4.0),但看起来很有趣,可选/命名参数(对.ctor)可能会在这里(映射到Readonly fields)... [更新:我已经在此这里 ]

有关信息,我可能不会制作这些方法virtual - 我们可能不希望子类能够使其不可冻结.如果您希望他们能够添加额外的代码,我会建议以下内容:

[public|protected] void Freeze()
{
    if(!frozen)
    {
        frozen = true;
        OnFrozen();
    }
}
protected virtual void OnFrozen() {} // subclass can add code here.

另外 - AOP(例如PostSharp)可能是添加所有这些ThrowFrozen()检查的可行选择.

(道歉,如果我更改了术语/方法名称 - 因此,在撰写回复时不可见原始帖子)

其他推荐答案

另一个选择是创建某种建筑商类.

举例来说,在Java(和C#以及许多其他语言)中,字符串是不可变的.如果要执行多个操作来创建字符串,则使用StringBuilder.这是可变的,然后完成后,您将其返回最终字符串对象.从那时起,它是不变的.

您可以为其他课程做类似的事情.您拥有不变的元素,然后是元素构造器.构建器所要做的就是存储您设置的选项,然后当您确定它时,它将构造并返回不可变的元素.

这是更多的代码,但我认为它比在本应不变的课程上设置了固定器要干净.

其他推荐答案

最初对我必须在每次修改上创建一个新的System.Drawing.Point的事实感到不适后,几年前,我完全接受了这个概念.实际上,我现在默认为readonly创建每个字段,并且只有在有令人信服的原因时才将其更改为可变的 - 这很少有.

我不太在乎跨线程问题,但是(我很少使用与此相关的代码).由于语义表达力,我发现它要好得多.不变性是一个界面的缩影,难以正确使用.

本文地址:https://www.itbaoku.cn/post/627604.html

问题描述

I have over the course of a few projects developed a pattern for creating immutable (readonly) objects and immutable object graphs. Immutable objects carry the benefit of being 100% thread safe and can therefore be reused across threads. In my work I very often use this pattern in Web applications for configuration settings and other objects that I load and cache in memory. Cached objects should always be immutable as you want to guarantee they are not unexpectedly changed.

Now, you can of course easily design immutable objects as in the following example:

public class SampleElement
{
  private Guid id;
  private string name;

  public SampleElement(Guid id, string name)
  {
    this.id = id;
    this.name = name;
  }

  public Guid Id
  {
    get { return id; }
  }

  public string Name
  {
    get { return name; }
  }
}

This is fine for simple classes - but for more complex classes I do not fancy the concept of passing all values through a constructor. Having setters on the properties is more desirable and your code constructing a new object gets easier to read.

So how do you create immutable objects with setters?

Well, in my pattern objects start out as being fully mutable until you freeze them with a single method call. Once an object is frozen it will stay immutable forever - it cannot be turned into a mutable object again. If you need a mutable version of the object, you simply clone it.

Ok, now on to some code. I have in the following code snippets tried to boil the pattern down to its simplest form. The IElement is the base interface that all immutable objects must ultimately implement.

public interface IElement : ICloneable
{
  bool IsReadOnly { get; }
  void MakeReadOnly();
}

The Element class is the default implementation of the IElement interface:

public abstract class Element : IElement
{
  private bool immutable;

  public bool IsReadOnly
  {
    get { return immutable; }
  }

  public virtual void MakeReadOnly()
  {
    immutable = true;
  }

  protected virtual void FailIfImmutable()
  {
    if (immutable) throw new ImmutableElementException(this);
  }

  ...
}

Let's refactor the SampleElement class above to implement the immutable object pattern:

public class SampleElement : Element
{
  private Guid id;
  private string name;

  public SampleElement() {}

  public Guid Id
  {
    get 
    { 
      return id; 
    }
    set
    {
      FailIfImmutable();
      id = value;
    }
  }

  public string Name
  {
    get 
    { 
      return name; 
    }
    set
    {
      FailIfImmutable();
      name = value;
    }
  }
}

You can now change the Id property and the Name property as long as the object has not been marked as immutable by calling the MakeReadOnly() method. Once it is immutable, calling a setter will yield an ImmutableElementException.

Final note: The full pattern is more complex than the code snippets shown here. It also contains support for collections of immutable objects and complete object graphs of immutable object graphs. The full pattern enables you to turn an entire object graph immutable by calling the MakeReadOnly() method on the outermost object. Once you start creating larger object models using this pattern the risk of leaky objects increases. A leaky object is an object that fails to call the FailIfImmutable() method before making a change to the object. To test for leaks I have also developed a generic leak detector class for use in unit tests. It uses reflection to test if all properties and methods throw the ImmutableElementException in the immutable state. In other words TDD is used here.

I have grown to like this pattern a lot and find great benefits in it. So what I would like to know is if any of you are using similar patterns? If yes, do you know of any good resources that document it? I am essentially looking for potential improvements and for any standards that might already exist on this topic.

推荐答案

For info, the second approach is called "popsicle immutability".

Eric Lippert has a series of blog entries on immutability starting here. I'm still getting to grips with the CTP (C# 4.0), but it looks interesting what optional / named parameters (to the .ctor) might do here (when mapped to readonly fields)... [update: I've blogged on this here]

For info, I probably wouldn't make those methods virtual - we probably don't want subclasses being able to make it non-freezable. If you want them to be able to add extra code, I'd suggest something like:

[public|protected] void Freeze()
{
    if(!frozen)
    {
        frozen = true;
        OnFrozen();
    }
}
protected virtual void OnFrozen() {} // subclass can add code here.

Also - AOP (such as PostSharp) might be a viable option for adding all those ThrowIfFrozen() checks.

(apologies if I have changed terminology / method names - SO doesn't keep the original post visible when composing replies)

其他推荐答案

Another option would be to create some kind of Builder class.

For an example, in Java (and C# and many other languages) String is immutable. If you want to do multiple operations to create a String you use a StringBuilder. This is mutable, and then once you're done you have it return to you the final String object. From then on it's immutable.

You could do something similar for your other classes. You have your immutable Element, and then an ElementBuilder. All the builder would do is store the options you set, then when you finalize it it constructs and returns the immutable Element.

It's a little more code, but I think it's cleaner than having setters on a class that's supposed to be immutable.

其他推荐答案

After my initial discomfort about the fact that I had to create a new System.Drawing.Point on each modification, I've wholly embraced the concept some years ago. In fact, I now create every field as readonly by default and only change it to be mutable if there's a compelling reason – which there is surprisingly rarely.

I don't care very much about cross-threading issues, though (I rarely use code where this is relevant). I just find it much, much better because of the semantic expressiveness. Immutability is the very epitome of an interface which is hard to use incorrectly.