On 5/31/11 1:26 PM, Nate wrote: [...]
By "automatically", I mean that I do not want to have to write code
that calls each member object's destruction method, as that is not a
safe programming idiom (an engineer may add more member variables down
the line and forget to add calls to their finalisation method).
Most member fields should not need any treatment whatsoever. The only
cleanup your code should worry about is when dealing with resources that
.NET itself cannot be aware of, either directly or indirectly (by
holding a reference to an object that implements IDisposable).
I tried to make this clearer in my discussion of actors. Making
states IDisposable does not automate the lifetime management of their
members. Lifetime management is important not just for memory, but
system participation in general. Deterministic finalisation should
automate the finalisation of actors at the lifetime of the state. Not
doing so is bad design, as it leaves open the possibility of an
engineer forgetting to do the finalisation action explicitly. This is
the same common design argument for all duplication of tasks - whether
code duplication (where an engineer may updte one piece of code but
not it's duplicate), serialisation (engineers may add a member but
forget to serialise it), or managing type information as switch-on-
enums (updating switch statements may miss a switch for a new enum) -
in general you should reduce the specification an engineer must update
to add functionality and prevent duplication of specification to
prevent divergence. In the examples, the common design solutions are
to use the same code (separate into a function), use introspective
serialisation, and use encapsulation into objects.
It's probably wrong to think of finalisation as needing to occur for
"resources" because most people think only OS resources like files,
windows, graphics surfaces, etc. Any actors that require finalisation
as a part of lifetime management must be deterministically finalised.
Some examples of actors that don't involve resources include:
- regulatory logs of security activity (events of opening and closing
doors on secure devices, for instance - the finalisation event being
the logging of the closing door or leaving the open state)
- notification data while in some state - addition and removal in the
UI model (not to be confused with the UI graphic resources, but the
actual data in the model when using proper MVC / MVP tripartite
presentation designs)
- communication with a host system on state notification (an "exiting
state X" communication found in many protocols)
The whole point of using a state pattern is that these things should
become automated, to reduce the O(N events x M states) complexity of
event handling to just those events that expect to be handled and to
reduce the O(M^2 state-to-state) transition complexity to O(M state)
construction/finalisation management.
Only for stack-allocated objects. In C#, one pretty much never
allocates reference-type objects on the stack.
You can't overload the assignment operator in C#, so that won't work
(note that in C++ the above is not the same as RAII; the "dispose on
assignment" works for a different reason, namely the implementation of
the overloaded assignment operator that goes with your smart-pointer class).
The use of the term RAII in architectural circles is that there should
never exist "zombie" objects, ie. objects which are not alive but not
quite dead. Two phase initialisation, multiphase destruction, etc.
all fall under the use of this term as espoused by Bjarne Stroustrup
(who coined the term). The point is that objects should be usable for
their lifetime (or that accessibility as objects should = their
usability lifetime) to prevent errors where engineers accidentally use
a zombie, and that in a language that allows exceptions, management of
zombies can be combinatorial in complexity.
So, my use here is fairly standard in those circles. I understand it
may not be understood the same way in c# circles, so I hope this
clarifies.
Properly encapsulated, it is trivial to add the necessary call to
IDisposable.Dispose() when updating the current state reference (e.g. by
using a single property or other method to handle the update, and
calling Dispose() there).
Again note that this has nothing to do with C++-style RAII, as the
objects are not allocated on the stack, nor does destruction happen as a
result of exiting a stack frame.
The whole point of my question is how to avoid the explicit calls to
all member actors in a state that would be placed inside the
Dispose(bool) if I used IDisposable. The IDisposable pattern does not
automate member finalisation - it must be done explicitly, which is
error prone and bad design. I'm looking for other patterns that
people may have in c# that solve this design issue (which is most
certainly about RAII - RAII has nothing to do with stack-v-heap).
There's no reference counting in .NET, except where you've implemented
it explicitly for some reason.
I didn't say anything of the sort. I was saying that management of
reference lifetimes ends when all references are no longer held, like
smart pointers managed by reference counts. The fact that the GC
walks reference graphs instead of using ref counting is immaterial to
the point - they act like smart pointers in one regard, but don't
provide the functionality (deterministic finalisation) that I need.
So I'm looking for something else.
Yet, it is .NET's equivalent to RAII, which is what you said you were
asking about (but of course as I've noted above, you've strayed from
that line of thought, so who really knows what you're asking).
It's a shame that many c# users have such a limited view of RAII,
because that totally misses Bjarne's original point.
I believe you are overlooking the usefulness of the IDisposable pattern.
Unfortunately, you have not actually posted any concrete code example of
a scenario showing what you'd like to do and why IDisposable or some
other approach doesn't help.
-------------- Program.cs ---------------------
namespace TestStateMachine
{
class Program
{
static void Main(string[] args)
{
// Make a state machine (default state 1) and fire event 1
StateMachine.Instance.Event1();
// fire event 2
StateMachine.Instance.Event2();
// fire event 3
StateMachine.Instance.Event3();
System.Threading.Thread.Sleep(10000);
}
}
}
--------- StateMachine.cs -------------
namespace TestStateMachine
{
public sealed class StateMachine
{
private static readonly StateMachine instance_ = new
StateMachine();
private StateBase currentState_ = new State1();
private StateMachine()
{ }
public static StateMachine Instance
{
get
{
return instance_;
}
}
public void Event1()
{
currentState_.Event1();
}
public void Event2()
{
currentState_.Event2();
}
public void Event3()
{
currentState_.Event3();
}
public void Transition(StateBase newState)
{
currentState_.Dispose();
currentState_ = newState;
}
};
}
------------ StateBase.cs ------------
using System;
namespace TestStateMachine
{
public interface StateBase : IDisposable
{
void Event1();
void Event2();
void Event3();
}
}
------------ State1.cs -----------------
using System;
namespace TestStateMachine
{
public class State1 : StateBase
{
TestManageThisClass someObject = new TestManageThisClass();
public State1()
{
Console.WriteLine("Entered State1");
}
~State1()
{
Console.WriteLine("Leaving State1");
Dispose(false);
}
public void Event1()
{
if (disposed_) throw new
ObjectDisposedException("State1");
StateMachine.Instance.Transition(new State2());
}
public void Event2()
{
if (disposed_) throw new
ObjectDisposedException("State1");
StateMachine.Instance.Transition(new State3());
}
public void Event3()
{
if (disposed_) throw new
ObjectDisposedException("State1");
StateMachine.Instance.Transition(new State4());
}
private bool disposed_ = false;
protected virtual void Dispose(bool disposing)
{
Console.WriteLine("State1 in Dispose");
if (!disposed_)
{
if (disposing)
{
// do nothing for managed to illustrate that
members are not deterministic
}
Console.WriteLine("State1 now disposed");
disposed_ = true;
}
}
public void Dispose()
{
Console.WriteLine("State1 in outer Dispose");
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
-------------------- TestManageThisClass.cs -----------------
using System;
namespace TestStateMachine
{
public class TestManageThisClass
{
public TestManageThisClass()
{
Console.WriteLine("TestManageThisClass ctor");
}
~TestManageThisClass()
{
Console.WriteLine("TestManageThisClass dtor");
}
}
}
// then similar for states 2 - 4 (left out for brevity)
--------------------------------------------------
When I run my program, the output is:
TestManageThisClass ctor
Entered State1
Entered State2
State1 in outer Dispose
State1 in Dispose
State1 now disposed
Entered State4
State2 in outer Dispose
State2 in Dispose
State2 now disposed
Entered State3
State4 in outer Dispose
State4 in Dispose
State4 now disposed
....
Notice that the contained object TestManageThisClass of State1 is not
automatically and deterministically finalised. Instead, I would have
to explicitly write in the Dispose(bool) method where I've left a
comment an explicit call to finalisation, likely by deriving the
TestManageThisClass type from IDisposable and callng Dispose(). As
I've tried to point out, this is widely acknowledged to be a bad
design and error prone, because it requires that an engineer duplicate
the specification of member actors in the state in the member list and
in the Dispose(bool) method, introducing the possibility of
divergence.
I mentioned the patterns I am aware of (using, IDisposable) for
deterministic finalisation. None of these solve the problem for
automating state based lifetime management. I was hoping someone
might offer another means of solving this that is widely used, as I
hope there are some c# developers out there that use good
architectural practices and have solved this. Possibly using T4? Or
reflection?
There are 4 lifetimes that are commonly used in architecting programs:
temporary - commonly anonymous objects that live for a statement or
between sequence points
scope - imperative objects used to perform procedural calculations
within a function or more explicit scope
state - actors that operate for the lifetime of a specific program
state - ie. between asynchronous events
application - objects that exist for the lifetime of the application
It is clear that Microsoft eventually realised the necessity for
deterministic finalisation of scope objects, because they created a
number of statements in c# to manage these (lock, using, ...).
Temporary objects have a number of useful idioms in other languages
that take advantage of deterministic finalisation (like expression
templates and expression proxies), but I don't need those for my
project right now, so I'm not asking about those yet (they are also
more advanced techniques, so I think I'd be less likely to get
answers). Application objects already have proper lifetime
management.
It is the scope lifetimes that I would hope I could fnd some well
known idioms to use, as they should have been developed with any
project that uses good architectural practices.
Is this any clearer?