Я хочу реализовать шаблон состояния типа на C#. Я пробовал что-то вроде:
interface Open {}
interface Closed {}
abstract class Foo<T> { }
class Bar<T> where T: Closed { }
class Bar<T> where T: Open { }
а также другие перестановки, но все они заканчиваются жалобами компилятора. Этот конкретный пример дает (через https://www.programiz.com/csharp-programming/online-compiler/):
/tmp/qmk1tTLHWG.cs(12,11): error CS0101: The namespace 'baz' already contains a definition for 'Bar'
/tmp/qmk1tTLHWG.cs(11,11): error CS0265: Partial declarations of 'Bar<T>' have inconsistent constraints for type parameter 'T'
Я недостаточно знаю о системе типов C#, чтобы знать, что на самом деле будет работать, и не могу найти подробной документации о том, как это сделать. Можно ли реализовать шаблон состояния типа? Я нашел блог Адама , однако на самом деле он не реализует шаблон состояния типа, как показано в 1, и это то, что я хотел бы сделать, если это возможно.
В идеале мне бы хотелось что-то вроде
struct Closed { };
struct Open { };
class Foo<T> {
// functions applicable to Foo's in both the Closed and Open states
}
class Foo<Closed> {
// functions applicable to Foo's in just the Open state
}
class Foo<Open> {
// functions applicable to Foo's in just the Open state
}
Можете ли вы создать базовый класс Foo и два подкласса FooClosed : Foo и FooOpen : Foo?
Я бы предпочел не создавать FooClosed и FooOpen, если язык поддерживает это, тем более что конечный автомат, который я пытаюсь реализовать, довольно сложен и, вероятно, будет иметь много разных вариантов FooXYZ.
Состояние типа — это идиома, а не шаблон проектирования. Это специфичное для языка решение проблемы создания конечного автомата с проверкой во время компиляции. Функциональные языки, такие как F#, будут использовать для этого размеченные объединения. В C# пока нет DU. Вы можете использовать сопоставление шаблонов, чтобы получить код, аналогичный F#, или использовать другие шаблоны, чтобы компилятор проверял, что все допустимые переходы сопоставлены.
Является ли шаблон состояния типа просто разновидностью свободного интерфейса?
@beyarkay, какой на самом деле конечный автомат? Вы опубликовали то, как, по вашему мнению, должна выглядеть реализация, а не сам конечный автомат. Если вам нужна безопасность типов, вы можете добавить в свой проект библиотеку F# и реализовать конечный автомат всего за несколько строк, которые можно будет повторно использовать в остальной части проекта. Проверьте эту реализацию автомата с 3 состояниями, например
Или вы ищете что-то вроде этого: dotnetfiddle.net/KVUUdd ?
@Fildor, это похоже на ответ Джонатана (stackoverflow.com/a/78677770/14555505), который я принял.
@beyarkay Я набирал скрипку и комментировал, прежде чем обновить страницу ... заметьте одно отличие: в моей скрипке у меня есть явные реализации интерфейса, позволяющие скрыть методы, специфичные для состояния, по прямым Foo ссылкам.
@DavidG шаблон typestate позволяет, среди прочего, определять зависимости между состояниями. Подробности я рекомендую прочитать в связанном сообщении в блоге.
@beyarkay в связанном сообщении блога говорится о типах возврата API, аналогично тому, как операторы LINQ возвращают различные типы IQueryables, которые допускают только определенные операции. Вы не можете применить thenByDescending к любому IQueryable, он работает только с IOrderedQueryable, возвращаемым только OrderBy. Это не то же самое, что типобезопасный конечный автомат.
@beyarkay Этот Foo класс можно явно привести к неправильному типу, минуя проверку типа. Таким образом кто-то может успешно вызвать Open на «открытом» объекте. Однако если бы методы были преобразованы в методы расширения, приведение Foo к IOpen или IClosed было бы невозможно.
@Fildor Явные реализации на самом деле не нужны, если Foo нельзя создать экземпляр напрямую.
Я бы также создал FooOpen и FooClosed и сделал все эти вещи internal так, чтобы их даже не мог увидеть внешний проект. Таким образом, вызывающие объекты имеют дело только с общедоступными интерфейсами и не могут приводить вещи в недопустимые состояния.
@ДжонатанБарклай Верно. Просто указал на разницу с ОП. Я бы сделал это в Fluent-API.





Вы должны моделировать это через интерфейсы, но должен быть только один класс.
Пример:
interface IOpen
{
IClosed Close();
}
interface IClosed
{
IOpen Open();
}
class Foo : IOpen, IClosed
{
public IOpen Open()
{
return this;
}
public IClosed Close()
{
return this;
}
}
var foo = new Foo();
var open = foo.Open();
var closed = open.Close(); // Only Close() is allowed on an open foo
closed.Open(); // Only Open() is allowed on an closed foo
Вы можете расширить это, установив состояние по умолчанию для Foo, используя фабричный метод:
class Foo : IOpen, IClosed
{
private Foo() // Prevent manual instantiation (using new)
{ }
public static IClosed Create() // Return closed by default
{
return new Foo();
}
public IOpen Open()
{
return this;
}
public IClosed Close()
{
return this;
}
}
var closed = Foo.Create();
var open = closed.Open(); // Only Open() is allowed by default
«это то, что я хотел бы сделать, если это возможно» — это не так. По крайней мере, не так, как это реализовано в первом фрагменте.