是否有一种通用模式在c#中使用自动状态退出操作赋予对象表达式或状态生命周期?

时间:2011-08-29 21:20:07

标签: c# state raii

在命令式应用程序中,对象有四个基本生命周期:

  1. 表达式(临时)生命周期
  2. 范围有效期
  3. 州寿命(事件之间)
  4. 应用程序有效期
  5. 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派生)将在调用状态对象的AutoDispose时调用Dispose。另外,我使其递归以支持对象重用。这样就无需在每个状态下编写清理代码,而是允许我的状态机在转换代码中一次调用AutoDispose。

    然而,这有许多缺点,包括:

    • 面对异常时无法回收对象
    • 确定每次状态转换时在运行时调用的方法,而不是每个类(希望)运行时或(最佳世界)转换时间。
    • 基类反射方法就像人们可以获得的hackish / intrusive一样。

    我确信那里的优秀建筑师已经参与过c#中的项目并且必须解决这些问题。什么样的模式已经发展到表达和状态生命周期自动销毁?


    编辑:我写过关于我的州解决方案的缺点,但我忘了提到为什么我发现表达式或临时终身解决方案我倾向于使用不满意。从域语言的角度来看,对代理上的Dispose的调用将表达式标识为具有代理。通常,代理的要点是因为有一些表达式,有些没有它们,并且表达式在某一点上是否返回代理是一个实现细节。例如,你可能有

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

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

    第二种情况可能会插入一个特殊的格式化程序,它会持续Serialise调用行的长度,然后返回默认格式。必须添加一个Dispose调用意味着我知道一行有一个特殊的格式化程序对象,尽管我可能会尝试一般地做一些事情。然后,当只有代理需要采取任何操作时,我必须重新设计为MySerialisationStream类添加一个do-nothing Dispose。随着我在表达式中使用的类型数量的增加,这增加了组合的复杂性。

1 个答案:

答案 0 :(得分:1)

你可以在函数调用中使用语句和代码块作为lambda表达式“包装”以实现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; }
    }

更新:

如果你想使用装饰器,例如从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时,功能上同样如下:

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

请注意,decorator和memento都是在lambda表达式中创建的,因为我不希望在此表达式中另一个stream入口 - 它必须能够对调用链中前一个表达式的结果进行操作,对于构建fluent interfaces至关重要。

我在这里没有使用/ IDisposable,因为我认为装饰器不是确定性的。此外,如果返回代理或原始对象,这种方式无关紧要。