自动生成不可变类和匹配构建器类

时间:2010-02-09 18:47:32

标签: c# language-agnostic design-patterns immutability builder-pattern

存在哪些工具/库将采用结构并自动生成不可变包装器以及用于逐步构建新实例的“构建器”类?

示例输入:

struct Foo
{
    public int apples;
    public int oranges;
    public Foo Clone() {return (Foo) base.MemberwiseClone();}
}

示例输出:

public class ImmutableFoo // could probably be a struct
{
    private Foo snapshot;
    internal ImmutableFoo(Foo value) { this.snapshot = value; }
    public FooBuilder Builder() { return new FooBuilder(snapshot); }
    public int Apples { get { return snapshot.apples; } }
    public int Oranges { get { return snapshot.oranges; } }
}

public class FooBuilder
{
    private Foo state;

    public int Apples { get { return state.apples; } set { state.apples = value; } }
    public int Oranges { get { return state.oranges; } set { state.oranges = value; } }

    public FooBuilder() { }

    internal FooBuilder(Foo toCopy) { state = toCopy.Clone(); }

    public ImmutableFoo Build()
    {
        ImmutableFoo result = new ImmutableFoo(state);
        state = state.Clone();
        return result;
    }
}

这样的“工具”可以是IDE插件,也可以在运行时使用反射生成新类。

示例是在C#中,但我会对任何静态类型的OO语言(Java,Scala,C ++等)的解决方案感兴趣。

理想的功能:

  • 从构建器类
  • 中的struct重新创建方法
  • 从不可变类中的结构重新创建非破坏性方法(特别是Equals()GetHashCode()以及任何接口方法)
  • 还生成一个IFooReader接口,其中包含每个struct成员的只读属性,由不可变和构建器实现。
  • 如果字段的类具有不可变等效项,则在不可变类中使用不可变版本(另请参阅How do I create a builder in C# for an object that has properties that are referenc types?),例如: List - > ReadOnlyCollection或类似。
  • 或者将构建器类作为输入(构建器使用自动属性而不是委托给结构。)
  • 不需要预定义Clone方法

“你不应该使用这样的工具,因为......”也欢迎回答。

2 个答案:

答案 0 :(得分:4)

以下是四种可能的解决方案。

1)使用CodeDOM生成C#或VB代码。这还允许您使用visual studio扩展来在设计器文件中生成代码。类似于visual studio已经提供的一些内置工具 - 比如那些为Web服务调用生成包装器的工具。不幸的是,我对扩展Visual Studio知之甚少。

  • 优点 - 您可以在构建之前生成源。这样可以更轻松地从任何程序集中针对生成的类型编写代码。
  • 缺点 - 不是语言不可知。你坚持使用支持的语言。

2)使用Mono.Cecil库分析构建后的程序集。然后,您可以使用包含的新类型重新编写程序集。

  • 优点 - 语言不可知。
  • 缺点 - 如果将类型添加到定义了结构的同一程序集中,则无法在同一程序集中针对生成的不可变结构类型编写代码。如果将生成的类型放在新的程序集中,那么这是可能的。

3)使用PostSharp。我不太了解这个库,所以你可能无法在程序集中添加新类型,但我知道你可以将IL注入到方法中。它还有很多很好的东西,可以很容易地使用属性来做到这一点。所以你可以这样做 -

[GenerateImmutable]
struct Foo
{
    public int apples;
    public int oranges;
    public Foo Clone() {return (Foo) base.MemberwiseClone();}
}
  • 优点 - 语言不可知,AOP在PostSharp中更容易。
  • 缺点 - 与Mono.Cecil相同,也不确定是否可以使用PostSharp生成新类型。

4)使用内置的Reflection.Emit库生成具有不可变类型的新程序集。

  • 优点 - 语言不可知,没有第三方的东西。
  • 缺点 - 必须将生成的类型放入新程序集中。无法将它们添加到原始类型所在的同一装配中。

答案 1 :(得分:2)

为什么要打扰建造者?

你有一个(讨厌的)可变结构,但如果你必须使用它直接使用它而不是创建一个繁琐且不必要的构建器。

令我感到困扰的是,你有足够数量的这些结构让你觉得你需要自动生成这种包装。我的直觉反应是你做错了......

如果不可变包装器的目的只是存储快照,那么只需使用以下内容:

public struct Snapshot<T> where t : struct
{
    private readonly T data;
    public Snapshot(T value) { this.data = value; }
    public T Data { get { return data; } }
}

传入的结构保证永远不会再次更改,但您可以直接访问它上面的所有值(并且在调用底层get_Data函数时创建的副本上会对这些结果进行修改)