代码之家  ›  专栏  ›  技术社区  ›  finnw

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

  •  12
  • finnw  · 技术社区  · 14 年前

    存在哪些工具/库,它们将采用一个结构并自动生成一个不可变的包装器和一个“构建器”类来增量地构建新实例?

    示例输入:

    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、斯卡拉、C++等)都有兴趣。

    理想特征:

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

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

    2 回复  |  直到 14 年前
        1
  •  4
  •   Brandon Cuff    14 年前

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

    1)使用 CodeDOM 生成C或VB代码。这还允许您使用Visual Studio扩展在设计器文件中生成代码。类似于Visual Studio已经提供的一些内置工具,例如为Web服务调用生成包装器的工具等。不幸的是,我不太了解如何扩展Visual Studio。

    • pros-您可以在构建之前生成源代码。这使得针对任何程序集生成的类型编写代码更加容易。
    • 缺点-不是语言不可知论。您一直使用支持的语言。

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

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

    3)使用 PostSharp . 我不太了解这个库,所以您可能无法向程序集添加新类型,但我知道您可以将IL注入方法中。它也有很多好东西,可以很容易地用属性来完成这项工作。所以你可以这么做-

    [GenerateImmutable]
    struct Foo
    {
        public int apples;
        public int oranges;
        public Foo Clone() {return (Foo) base.MemberwiseClone();}
    }
    
    • 专业语言不可知论者, AOP 在后竖琴中更容易做。
    • 与mono.cecil相同,也不确定是否可以使用postsharp生成新类型。

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

    • 专业语言不可知论,没有第三方的东西。
    • cons-必须将生成的类型放入新程序集中。无法将它们添加到原始类型所在的程序集中。
        2
  •  2
  •   ShuggyCoUk    14 年前

    为什么要麻烦建筑商?

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

    您有足够数量的这些结构,使您觉得需要自动生成此类包装器,这让我有些不安。我的直觉反应是你做错了…

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

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