Существует ли общий шаблон для предоставления объектам выражения или времени жизни состояний в c # с помощью автоматических действий по выходу из состояния? - PullRequest
3 голосов
/ 30 августа 2011

Существует четыре основных срока службы объектов в императивных приложениях:

  1. Выражение (временное) время жизни
  2. Срок службы
  3. Время жизни состояния (между событиями)
  4. Срок службы приложения

c # был разработан без поддержки RAII, а затем добавил оператор using в качестве средства обеспечения необходимого механизма для автоматизации времени жизни области. Время жизни приложения - это управляемая область приложения, потому что закрытие приложения примерно настолько детерминировано, насколько это возможно для вызова сборки мусора. Это оставляет время жизни выражения и состояния необработанным с любым языковым механизмом для автоматического детерминированного уничтожения.

Существуют ли общие схемы, которые решают эти фундаментальные потребности в c #? Я уверен, что подобные решения общих проблем уже решались несколько раз, но, глядя в Интернете, я не могу найти никаких статей по этому вопросу.

В c ++ dtors во временных объектах времени жизни используются для предоставления прокси или оберток для выражений и встречаются в различных идиомах для изменения выражений, временного изменения состояний потока и продвинутых оптимизаций, таких как шаблоны выражений. Для состояний общее решение OO состоит в том, чтобы использовать шаблон State и помещать объекты с временем жизни данного состояния внутри объекта состояния в качестве членов. Таким образом, например, если у вас есть окно дисплея для данного состояния, ctor отобразит объект, а dtor удалит его из системы отображения.

Когда я выполняю поиск таких вещей, как «шаблоны выражений» или «шаблон состояний» в Интернете, полученные результаты не включают автоматические вызовы финализатора. Вместо этого я вижу вызовы для очистки состояния, оставленные в виде отдельного вызова функции (поэтому, если у вас есть несколько состояний с экранным объектом, как показано в примере, вы должны вызывать очистку в каждом состоянии, а не просто писать код очистки один раз в объекте, и аналогичным образом запомните это для всех объектов каждого состояния). И я вижу, что будет одним выражением, разбитым на несколько операторов, чтобы включить явное завершение временных значений.

Что касается временных, я обычно склонялся к

(/*expression that builds proxy at some point*/).Dispose();

в пользу многопоточных решений.

Для недавнего проекта для состояний жизни я сделал следующий класс

namespace libAutoDisposable
{
    public class AutoDisposable
    {
        public void AutoDispose()
        {
            // use reflection to get the fields of this
            FieldInfo[] infos = GetType().GetFields();

            // loop through members
            foreach (FieldInfo info in infos)
            {
                // now try to call AutoDispose or Dispose on each member
                if (typeof(AutoDisposable).IsAssignableFrom(info.FieldType))
                {
                    // get the value object
                    AutoDisposable value = (AutoDisposable)info.GetValue(this);

                    // and invoke
                    value.AutoDispose();
                }
                else if (typeof(IDisposable).IsAssignableFrom(info.FieldType))
                {
                    // get the value object
                    IDisposable value = (IDisposable)info.GetValue(this);

                    // so invoke
                    value.Dispose();
                }
            }
        }
    }
}

, который будет перебирать членов объекта состояния, и все объекты, нуждающиеся в финализации (т.е. производные от IDisposable), будут вызывать Dispose при вызове AutoDispose объекта состояния. Кроме того, я сделал рекурсивным поддержку повторного использования объектов. Это избавляет от необходимости писать код очистки в каждом состоянии, вместо этого позволяя моим автоматам вызывать AutoDispose один раз в коде перехода.

Однако у этого есть ряд недостатков, в том числе:

  • Оставляет объекты невостребованными перед лицом исключений
  • Определяет методы, вызываемые во время выполнения при каждом переходе состояния, а не один раз за класс (надеюсь) время выполнения или (лучшее из миров) время перевода.
  • Подход к отражению базового класса настолько хакерский / навязчивый, насколько это возможно.

Я уверен, что хорошие архитекторы работали над проектами в c # и должны были решить эти проблемы. Какие закономерности эволюционировали, чтобы дать автоматическое уничтожение выражений и состояний жизни?


РЕДАКТИРОВАТЬ: я писал о недостатках моего решения о состоянии, но я забыл упомянуть, почему я нахожу выражение или решение с временным временем жизни, которое я склонен использовать неудовлетворительно. С точки зрения языка домена, вызов Dispose на прокси-сервере идентифицирует выражение как имеющее прокси. Обычно смысл прокси заключается в том, что есть некоторые выражения с ними, а некоторые без них, и то, возвращает ли выражение в некоторой точке прокси, деталь реализации. Например, у вас может быть

mySerialisationStream.Serialise(obj1).Serialise(obj2);

и

mySerialisationStream.Serialise(specialFormatter).Serialise(obj1).Serialise(obj2);

Во втором случае может быть вставлен специальный форматер, который длится длину строки вызовов Serialise, а затем возвращается к форматированию по умолчанию.Необходимость добавить вызов Dispose означает, что я знаю, когда в строке есть специальный объект форматирования, хотя, возможно, я пытаюсь сделать что-то общее.Затем мне нужно либо изменить дизайн, чтобы добавить класс Dispose в MySerialisationStream, когда только прокси-сервер должен предпринимать какие-либо действия.Это добавляет комбинаторную сложность по мере увеличения числа типов, с которыми я работаю в выражении.

1 Ответ

1 голос
/ 15 сентября 2011

Вы можете 'обернуть', используя оператор вместе с блоком кода в качестве лямбда-выражения в вызове функции для достижения семантики RAII:

        var stringBuilder = new StringBuilder();
        var stream = new Stream(stringBuilder);

        stream
            .Serialize(1)
            .IsolateMemento(s=>new StreamMemento(s),s=>s
                .Serialize(new Formatter("formatted {0}"))
                .Serialize(2))
            .Serialize(3);

        Assert.AreEqual("1;formatted 2;3;", stringBuilder.ToString());

Основной частью является следующий метод расширения:

    public static class MementoExtensions
    {
        public static T IsolateMemento<T>(
            this T originator,
            Func<T, IDisposable> generateMemento,
            Func<T, T> map)
        {
            using (generateMemento(originator))
                return map(originator);
        }
    }

Детали реализации:

    public class Stream
    {
        public StringBuilder StringBuilder { get; set; }
        public Stream(StringBuilder stringBuilder) { StringBuilder = stringBuilder; }
        public Formatter Formatter = new Formatter("{0}");
        public Stream Serialize(object o)
        {
            var formatter = o as Formatter;
            if (formatter != null) Formatter = formatter;
            else StringBuilder.Append(Formatter.Format((o ?? "").ToString())).Append(";");
            return this;
        }
    }

    public class Formatter
    {
        public string FormatString { get; set; }
        public Formatter(string s) { FormatString = s; }
        public string Format(string s) { return string.Format(FormatString, s); }
    }

    public class StreamMemento : IDisposable
    {
        private Stream Originator { get; set; }
        private Formatter FormatterBefore { get; set; }
        public StreamMemento(Stream s) { Originator = s; FormatterBefore= s.Formatter; }
        public void Dispose() { Originator.Formatter = FormatterBefore; }
    }

UPDATE:

Если вы хотите использовать декораторы, например, возвращая декоратор из метода Serialize, можно использовать другое расширение:

public static T DecoratedAction<T>(
                this T originator,
                Func<T,T> createDecorator,
                Action<T> act)
            {
                var decorated = createDecorator(originator);
                act(decorated);
                return originator;
            }

Использование будет:

stream.Serialize(obj1).DecoratedAction(s=>s.Serialize(formatter), s=>s.Serialize(obj2)).Serialize(obj3)

когда Serialize (formatter) возвращает оформленный прокси-поток, функционально равный:

stream.Serialize(obj1).DecoratedAction(s=>new FormattedStream(formatter,s), s=>s.Serialize(obj2)).Serialize(obj3)

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

Я не использую здесь / IDisposable, потому что я предполагаю, что декораторы не предназначены для детерминированной утилизации. Кроме того, это не имеет значения, если возвращается прокси или исходный объект.

...