Автоматическая генерация неизменяемого класса и соответствующего класса строителя
Какие существуют инструменты / библиотеки, которые будут принимать структуру и автоматически генерировать неизменяемую оболочку, а также класс "строитель" для поэтапного создания новых экземпляров?
Пример ввода:
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)