Вопрос

For convenience and safety reasons i'd like to use the using statement for allocation and release of objects from/to a pool

public class Resource : IDisposable
{
    public void Dispose()
    {
        ResourcePool.ReleaseResource(this);
    }
}

public class ResourcePool
{
    static Stack<Resource> pool = new Stack<Resource>();

    public static Resource GetResource()
    {
        return pool.Pop();
    }

    public static void ReleaseResource(Resource r)
    {
        pool.Push(r);
    }
}

and the access the pool like

using (Resource r = ResourcePool.GetResource())
{
     r.DoSomething();
}

I found some topics on abusing using and Dispose() for scope handling but all of them incorporate using (Blah b = _NEW_ Blah()).
Here the objects are not to be freed after leaving the using scope but kept in the pool.
If the using statement simply expands to a plain try finally Dispose() this should work fine but is there something more happening behind the scenes or a chance this won't work in future .Net versions?

Это было полезно?

Решение

This is not an abuse at all - that is a common scope-handling idiom of C#. For example, ADO.NET objects (connections, statements, query results) are commonly enclosed in using blocks, even though some of these objects get released back to their pools inside their Dispose methods:

using (var conn = new SqlConnection(dbConnectionString)) {
    // conn is visible inside this scope
    ...
} // conn gets released back to its connection pool

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

It is a valid way to use IDisposable.

In fact, this is how connection pooling is also done in .NET - wrapping a DBConnection object in a using statement to ensure the connection closes and is returned to the connection pool.

TransactionScope is another example for a class that uses the Dispose pattern to rollback un-completed transactions:

A call to the Dispose method marks the end of the transaction scope.

If the using statement simply expands to a plain try finally Dispose() this should work fine but is there something more happening behind the scenes or a chance this won't work in future .Net versions?

It does. Your code should work fine, and is guaranteed by the spec to continue working the same way. In fact, this is fairly common (look at connection pooling in SQL, for a good example.)

The main problem with your code, as written, is that you could explicitly call ReleaseResource within a using, can cause the pool to get the resourced pushed more than once, since it's a part of the public API.

This looks like an abuse of IDisposable and a poor design decision to me. First, it forces the objects that are stored in the pool to know about the pool. That's akin to creating a List type that forces objects in it to implement a particular interface or derive from some particular class. Like a LinkedList class that forces data items to include Next and Previous pointers that the list can use.

In addition, you have the pool allocate the resource for you, but then the resource has a call to put itself back into the pool. That seems ... odd.

I think a better alternative would be:

var resource = ResourcePool.GetResource();
try
{
}
finally
{
    ResourcePool.FreeResource(resource);
}

It's a little bit more code (try/finally rather than using), but a cleaner design. It relieves the contained objects from having to know about the container, and it more clearly shows that the pool is managing the object.

Your understanding of the using statement is correct (try, finally, Dispose). I don't foresee this changing anytime soon. So many things would break if it did.

There's not necessarily anything wrong with what you're planning. I've seen this sort of thing before, where Dispose doesn't actually shut down the object, but rather puts it into some sort of state that isn't "fully operational."

If you are at all concerned about this, you can implement this in such a way that it adheres to the usual Dispose implementation pattern. Simply have a wrapper class that implements IDisposable and exposes all the methods of the underlying class. When the wrapper is disposed, put the underlying object into the pool, not the wrapper. Then you can consider the wrapper shut down, although the thing that it wrapped is not.

What you are doing is like C++ and RAII. And in C#, it is about as close that that C++/RAII idiom as you can get.

Eric Lippert, who knows a-thing-or-two about C#, is adamantly against using the IDispose and using statement as a C# RAII idiom. See his in depth response here, Is it abusive to use IDisposable and "using" as a means for getting "scoped behavior" for exception safety?.

Part of the problem with IDisposable used in this RAII fashion is that IDisposable has very stringent requirements to be used correctly. Almost all the C# code I've seen that uses IDisposable fails to implement the pattern correctly. Joe Duffy has made a blog post that goes into meticulous detail about the proper way to implement the IDisposable pattern http://joeduffyblog.com/2005/04/08/dg-update-dispose-finalization-and-resource-management/. Joe's information is much more detailed and extensive than what is mentioned on MSDN. Joe also knows a-thing-or-two about C#, and there were a lot of very smart contributors that helped flesh out that document.

Simple things can be done to implement the bare-bones-minimal IDisposable pattern (for use such as in RAII), such as sealing the class, and since there are no unmanaged resources not having a finalizer, and such. MSDN https://msdn.microsoft.com/en-us/library/system.objectdisposedexception%28v=vs.110%29.aspx is a nice overview, but Joe's information has all the gory details.

But the one thing that you cannot get away from with IDisposable is its "viral" nature. Classes that hold onto a member which is IDisposable themselves ought to become IDisposable... not a problem in the using(RAII raii = Pool.GetRAII()) scenario, but something to be very mindful about.

All that being said, despite Eric's position (of which I tend to agree with him on most everything else), and Joe's 50 page essay of how to properly implement the IDisposable pattern... I do use it as the C#/RAII idiom myself.

Now only if C# had 1) non-nullable references (like C++ or D or Spec#) and 2) deeply immutable data types (like D, or even F# [you can do the F# kind of immutable in C#, but it is a LOT of boilerplate, and its just too hard to get right... makes the easy hard, and the hard impossible]) and 3) design-by-contract as part of the language proper (like D or Eiffel or Spec#, not like the C# Code Contracts abomination). sigh Maybe C# 7.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top