开发者

Is there a common pattern to give objects Expression or State lifetimes in c# with automated state-leaving actions?

There are four basic lifetimes of objects in imperative applications:

  1. Expression (temporary) lifetime
  2. Scope lifetime
  3. State lifetimes (between events)
  4. Application lifetime

c# was infamously designed without RAII support, and later added the using statement as a means of providing the necessary mechanism for automating scope lifetimes. Application lifetimes are managed application scope because application close is about as deterministic as you can get for invoking garbage collection. This leaves expression and state lifetimes unhandled with any language mechanism for automated deterministic destruction.

Are there common patterns that solve these fundamental needs in c#? I am sure that these kinds of solutions to a common need have been solved a number of times by now, but looking online, I cannot find any articles on this.

In c++, dtors in temporary lifetime objects are used to provide proxies or wrappers for expressions, and are found in a variety of idioms for modfying expressions, altering stream states temporarily, and advanced optimisations like expression templates. For states, the common OO solution is to use the State pattern and to put objects with the lifetime of a given state inside the state object as members. In this way, for instance, if you have a display box on the screen for a given state, the ctor will display the object and the dtor will remove it from the display system.

When I do a search for things like "expression templates" or "state pattern" online, the results I get do not include automated finaliser calls. Instead, I see the calls to cleanup a state left as a separate function call (so if you have multiple states with a display object, as the example goes, you have to call cleanup in each state, instead of just writing the cleanup code once in the object, and similarly remember this for all objects of each state). And I see what would be single expressions broken up into multiple statements to incorporate the explicit finalisation of temporaries.

For temporaries, I have typically leaned towards

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

in favor of multiline solutions.

For a recent project for state lifetimes, I made the following class

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();
                }
            }
        }
    }
}

which would iterate over the members of a state object, and all objects needing finalisatio开发者_开发知识库n (ie. deriving from IDisposable) would call Dispose when the state object's AutoDispose was called. Additionally, I made it recursive to support object reuse. This prevents the need for having to write cleanup code in each state, instead allowing my state machines to call AutoDispose a single time in the transition code.

However, this has a number of downsides, including:

  • Leaves objects unreclaimed in the face of exceptions
  • Determines the methods to call at runtime each time a state transitions, instead of once per class (hopefully) runtime or (best-of-worlds) translationtime.
  • The base class reflection approach is about as hackish / intrusive code as one can get.

I'm sure good architects out there have worked on projects in c# and had to solve these issues. What patterns have evolved to give expression and state lifetimes automated destruction?


EDIT: I wrote about the shortcomings of my state solution, but I forgot to mention why I find the expression or temporary lifetime solution I tend to use unsatisfactory. From a domain language point of view, the call to Dispose on the proxy identifies the expression as having the proxy. Usually, the point of the proxy, though, is because there are some expressions with them and some without them, and whether or not the expression at some point returns a proxy is an implementation detail. As an example, you might have

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

and

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

The second case might insert a special formatter that lasts the length of the line of Serialise calls and then returns to default formatting. Having to add a Dispose call means I know when a line has a special formatter object, though I may be trying to do things generically. I then either have to redesign to add a do-nothing Dispose to MySerialisationStream class, when only the proxy needs to take any action. This adds combinatorial complexity as the number of types I'm working with in an expression increases.


You can 'wrap' using statement along with a code block as lambda expression in a function call to achieve RAII semantics:

        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());

The main part is following extension method:

    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);
        }
    }

Implementation details:

    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:

If you want to use decorators, for example returning decorator from Serialize method, another extension can be used:

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

A usage would be:

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

when Serialize(formatter) returns a decorated proxy Stream, functionally equally to:

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

Note that decorator and memento both created in a lambda expression, because i don't want another entrance of stream in this expression - it must be able to operate on a result of previous expression in call chain, which is vital for building fluent interfaces.

I'm not using using/IDisposable here, because i assume decorators are not meant to be deterministically disposed. Also, it does not matter this way, if a proxy or the original object is returned.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜