Шаблон состояния типа в C#

Я хочу реализовать шаблон состояния типа на 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
}

«это то, что я хотел бы сделать, если это возможно» — это не так. По крайней мере, не так, как это реализовано в первом фрагменте.

Fildor 27.06.2024 14:49

Можете ли вы создать базовый класс Foo и два подкласса FooClosed : Foo и FooOpen : Foo?

SomeBody 27.06.2024 14:50

Я бы предпочел не создавать FooClosed и FooOpen, если язык поддерживает это, тем более что конечный автомат, который я пытаюсь реализовать, довольно сложен и, вероятно, будет иметь много разных вариантов FooXYZ.

beyarkay 27.06.2024 14:52

Состояние типа — это идиома, а не шаблон проектирования. Это специфичное для языка решение проблемы создания конечного автомата с проверкой во время компиляции. Функциональные языки, такие как F#, будут использовать для этого размеченные объединения. В C# пока нет DU. Вы можете использовать сопоставление шаблонов, чтобы получить код, аналогичный F#, или использовать другие шаблоны, чтобы компилятор проверял, что все допустимые переходы сопоставлены.

Panagiotis Kanavos 27.06.2024 14:53

Является ли шаблон состояния типа просто разновидностью свободного интерфейса?

DavidG 27.06.2024 14:54

@beyarkay, какой на самом деле конечный автомат? Вы опубликовали то, как, по вашему мнению, должна выглядеть реализация, а не сам конечный автомат. Если вам нужна безопасность типов, вы можете добавить в свой проект библиотеку F# и реализовать конечный автомат всего за несколько строк, которые можно будет повторно использовать в остальной части проекта. Проверьте эту реализацию автомата с 3 состояниями, например

Panagiotis Kanavos 27.06.2024 14:54

Или вы ищете что-то вроде этого: dotnetfiddle.net/KVUUdd ?

Fildor 27.06.2024 15:00

@Fildor, это похоже на ответ Джонатана (stackoverflow.com/a/78677770/14555505), который я принял.

beyarkay 27.06.2024 15:05

@beyarkay Я набирал скрипку и комментировал, прежде чем обновить страницу ... заметьте одно отличие: в моей скрипке у меня есть явные реализации интерфейса, позволяющие скрыть методы, специфичные для состояния, по прямым Foo ссылкам.

Fildor 27.06.2024 15:07

@DavidG шаблон typestate позволяет, среди прочего, определять зависимости между состояниями. Подробности я рекомендую прочитать в связанном сообщении в блоге.

beyarkay 27.06.2024 15:07

@beyarkay в связанном сообщении блога говорится о типах возврата API, аналогично тому, как операторы LINQ возвращают различные типы IQueryables, которые допускают только определенные операции. Вы не можете применить thenByDescending к любому IQueryable, он работает только с IOrderedQueryable, возвращаемым только OrderBy. Это не то же самое, что типобезопасный конечный автомат.

Panagiotis Kanavos 27.06.2024 15:11

@beyarkay Этот Foo класс можно явно привести к неправильному типу, минуя проверку типа. Таким образом кто-то может успешно вызвать Open на «открытом» объекте. Однако если бы методы были преобразованы в методы расширения, приведение Foo к IOpen или IClosed было бы невозможно.

Panagiotis Kanavos 27.06.2024 15:13

@Fildor Явные реализации на самом деле не нужны, если Foo нельзя создать экземпляр напрямую.

Johnathan Barclay 27.06.2024 15:17

Я бы также создал FooOpen и FooClosed и сделал все эти вещи internal так, чтобы их даже не мог увидеть внешний проект. Таким образом, вызывающие объекты имеют дело только с общедоступными интерфейсами и не могут приводить вещи в недопустимые состояния.

DavidG 27.06.2024 15:22

@ДжонатанБарклай Верно. Просто указал на разницу с ОП. Я бы сделал это в Fluent-API.

Fildor 27.06.2024 17:07
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
15
130
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы должны моделировать это через интерфейсы, но должен быть только один класс.

Пример:

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

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