13

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

示例输入:

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++ 等)的解决方案感兴趣。

理想的功能:

  • 从构建器类中的结构重新创建方法
  • 从不可变类中的结构重新创建非破坏性方法(尤其是Equals()GetHashCode()任何接口方法)
  • 还为每个结构成员生成一个IFooReader包含只读属性的接口,由不可变和构建器实现。
  • 如果字段的类具有不可变等效项,则在不可变类中使用不可变版本(另请参阅如何在 C# 中为具有引用类型属性的对象创建构建器?)例如List->ReadOnlyCollection或类似的。
  • 或者将构建器类作为输入(构建器使用自动属性而不是委托给结构。)
  • 不需要Clone预定义方法

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

4

2 回答 2

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库来生成具有不可变类型的新程序集。

  • 优点 - 语言不可知,没有第 3 方的东西。
  • 缺点 - 必须将生成的类型放入新程序集中。无法将它们添加到原始类型所在的同一程序集中。
于 2010-02-12T02:22:52.483 回答
1

为什么要打扰建造者?

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

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

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

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 函数时创建的副本上)

于 2010-02-13T18:00:14.570 回答