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

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

问题描述

我已经完成了一些项目的开发过程,用于创建不可变(只读)对象和不可变对象图形的模式。不变的对象具有100%线程安全的优点,因此可以跨线程重用。在我的工作中,我经常在Web应用程序中使用此模式进行配置设置和其他加载和缓存在内存中的对象。缓存的对象应该永远是不变的,因为你想保证它们没有意外地改变。



现在,你可以很容易地设计不可变对象,如下例所示: p>

  public class SampleElement 
{
private Guid id;
私有字符串名称;

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创建不可变对象?



嗯,在我的模式对象开始是完全可变,直到你用单一的方法调用冻结它们。一旦对象被冻结,它将永远保持不变 - 它不能再变成可变对象。如果你需要一个可变版本的对象,你只需要克隆它。



好的,现在看一些代码。我在下面的代码片段中尝试将模式调整到最简单的形式。 IElement是所有不可变对象必须最终实现的基础接口。

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

Element类是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);
}

...
}

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

  public class SampleElement:Element 
{$ b $私人指导
私有字符串名称;

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。



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



我已经发展成为喜欢这种模式很多,并发现很大的好处。那么我想知道的是,如果你们中的任何一个使用类似的模式?如果是的话,你知道有什么好的资源记录吗?我本质上正在寻找潜在的改进,以及可能已经存在的这个主题的任何标准。

解决方案

对于信息,第二种方法被称为冰棒不变性。



Eric Lippert有一系列关于不变性的博客条目,起始于 here 。我仍然需要掌握CTP(C#4.0),但它看起来很有趣,可选/命名参数(.ctor)可能在这里(映射到只读字段)...
[更新:我已经在这个这里上发表了文章。



对于信息,我可能不会使这些方法 virtual - 我们可能不希望子类能够不冻结。如果您希望他们能够添加额外的代码,我会建议如下:

  [public | protected] void冻结()
{
if(!frozen)
{
frozen = true;
OnFrozen();
}
}
protected virtual void OnFrozen(){} //子类可以在这里添加代码。

另外 - AOP(如PostSharp)可能是添加所有这些ThrowIfFrozen()检查的可行选项



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


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)

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

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