Автоматическая генерация неизменяемого класса и соответствующего класса строителя

Какие существуют инструменты / библиотеки, которые будут принимать структуру и автоматически генерировать неизменяемую оболочку, а также класс "строитель" для поэтапного создания новых экземпляров?

Пример ввода:

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#, но мне было бы интересно найти решение для любого языка ОО со статической типизацией (Java, Scala, C++ и т. Д.).

Желательные особенности:

  • Воссоздает методы из структуры в классе построителя
  • Воссоздает неразрушающие методы из структуры в неизменяемом классе (особенно Equals() а также GetHashCode() и любые методы интерфейса)
  • Также генерирует IFooReader интерфейс, содержащий свойства только для чтения для каждого члена структуры, реализованные как неизменяемым, так и сборщиком.
  • Если у класса поля есть неизменяемый эквивалент, используется неизменяемая версия в неизменяемом классе (см. Также Как создать построитель в C# для объекта, обладающего свойствами, являющимися ссылочными типами?), Например List -> ReadOnlyCollection или похожие.
  • В качестве альтернативы возьмите класс построителя в качестве входных данных (где построитель использует автоматические свойства вместо делегирования в структуру.)
  • Не требует Clone метод должен быть предопределен

"Вы не должны использовать такой инструмент, потому что..." ответы также приветствуются.

2 ответа

Решение

Вот четыре возможных решения.

1) Используйте CodeDOM для генерации кода C# или VB. Это также позволит вам использовать расширения Visual Studio для генерации вашего кода в дизайнерских файлах. Подобно некоторым встроенным инструментам, которые уже предлагает Visual Studio, например, тем, которые генерируют оболочки для вызовов веб-служб и т. Д. К сожалению, я не знаю много о расширении Visual Studio.

  • Плюсы - Вы можете создать источник до сборки. Это облегчает написание кода для сгенерированных типов из любой сборки.
  • Минусы - не языковой агностик. Вы застряли с языками, которые поддерживаются.

2) Используйте библиотеку Mono.Cecil для анализа сборки после сборки. Затем вы можете переписать сборку с добавлением новых типов.

  • Плюсы - язык агностик.
  • Минусы - если вы добавите типы в ту же сборку, в которой определены ваши структуры, вы не сможете писать код для сгенерированных неизменяемых структурных типов в той же сборке. Если вы поместите сгенерированные типы в новую сборку, это возможно.

3) Используйте PostSharp. Я не так много знаю об этой библиотеке, поэтому вы не сможете добавлять новые типы в вашу сборку, но я знаю, что вы можете внедрить IL в методы. Он также имеет много хороших вещей, которые позволяют легко сделать это с атрибутами. Так что вы могли бы сделать это -

[GenerateImmutable]
struct Foo
{
    public int apples;
    public int oranges;
    public Foo Clone() {return (Foo) base.MemberwiseClone();}
}
  • Плюсы - независимость от языка, АОП легче сделать в PostSharp.
  • Минусы - То же, что и с Mono.Cecil, а также не уверен, что вы можете создавать новые типы, используя PostSharp.

4) Используйте встроенные библиотеки Reflection.Emit для генерации новой сборки с вашими неизменяемыми типами.

  • Плюсы - независимость от языка, никаких сторонних материалов.
  • Минусы - должны помещать сгенерированные типы в новые сборки. Невозможно добавить их в ту же сборку, в которой находится исходный тип.

Зачем возиться с застройщиком?

У вас есть (неприятная) изменчивая структура, но если она вам нужна, используйте ее напрямую, а не создавайте громоздкий и ненужный Строитель.

Меня несколько беспокоит, что у вас есть достаточное количество этих структур, чтобы вы чувствовали, что вам нужно автоматически создавать обертки такого типа. Моя внутренняя реакция такова, что вы делаете это неправильно...

Если цель неизменяемой оболочки - просто сохранить снимок, тогда просто используйте что-то вроде этого:

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)

Другие вопросы по тегам