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

查看:26
本文介绍了C# 中的不可变对象模式 - 你怎么看?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在几个项目的过程中开发了一种模式来创建不可变(只读)对象和不可变对象图.不可变对象具有 100% 线程安全的优点,因此可以跨线程重用.在我的工作中,我经常在 Web 应用程序中使用这种模式来进行配置设置和其他加载并缓存在内存中的对象.缓存对象应该始终是不可变的,因为您希望保证它们不会被意外更改.

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; }
  }
}

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

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.

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

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.

好的,现在开始一些代码.我在以下代码片段中尝试将模式归结为最简单的形式.IElement 是所有不可变对象最终必须实现的基本接口.

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();
}

Element 类是 IElement 接口的默认实现:

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);
  }

  ...
}

让我们重构上面的 SampleElement 类来实现不可变对象模式:

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;
    }
  }
}

您现在可以更改 Id 属性和 Name 属性,只要该对象尚未通过调用 MakeReadOnly() 方法标记为不可变.一旦它是不可变的,调用 setter 将产生一个 ImmutableElementException.

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.

最后说明:完整模式比此处显示的代码片段更复杂.它还包含对不可变对象集合和不可变对象图的完整对象图的支持.完整模式使您能够通过调用最外层对象的 MakeReadOnly() 方法使整个对象图不可变.一旦您开始使用这种模式创建更大的对象模型,泄漏对象的风险就会增加.泄漏对象是在对对象进行更改之前未能调用 FailIfImmutable() 方法的对象.为了测试泄漏,我还开发了一个用于单元测试的通用泄漏检测器类.它使用反射来测试所有属性和方法是否在不可变状态下抛出 ImmutableElementException.换句话说,这里使用的是 TDD.

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 有一系列关于不变性的博客条目,从 此处.我仍在掌握 CTP (C# 4.0),但它看起来很有趣可选/命名参数(对 .ctor)可能在这里做什么(当映射到只读字段时)......[更新:我在这个这里上写了博客]

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]

有关信息,我可能不会将这些方法设为 virtual - 我们可能不希望子类能够使其不可冻结.如果您希望他们能够添加额外的代码,我建议您这样做:

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.

此外 - AOP(例如 PostSharp)可能是添加所有 ThrowIfFrozen() 检查的可行选项.

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)

这篇关于C# 中的不可变对象模式 - 你怎么看?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆