Автоматическая генерация неизменяемого класса и соответствующего класса строителя - PullRequest
12 голосов
/ 09 февраля 2010

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

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

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 ]

4 голосов
/ 12 февраля 2010

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

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();}
}
  • Плюсы - независимость от языка, AOP проще сделать в PostSharp.
  • Минусы - То же, что и с Mono.Cecil, а также не уверен, что вы можете создавать новые типы с помощью PostSharp.

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

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

Зачем беспокоиться о строителе?

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

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

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

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)

...