In last week’s post we saw how we can use IDisposable and finalizers to automate our previously developed generic object pooling.
Today we will take a look at how to abstract further, and create a generic base class that can be used to handle the pooling of any number of objects.
Recap
Last week’s result was the following poolable Swimmer
class:
class Swimmer : IDisposable
{
private const int disposedFalse = 0;
private const int disposedTrue = 1;
private int disposed = disposedFalse;
public static Swimmer GetOne()
{
var swimmer = StaticObjectPool
.PopOrDefault<Swimmer>()
?? new Swimmer();
swimmer.disposed = disposedFalse;
return swimmer;
}
private Swimmer()
{
}
public void Dispose()
{
if(Interlocked.Exchange(
ref this.disposed, disposedTrue
) == disposedTrue)
return;
StaticObjectPool.Push(this);
}
~Swimmer()
{
this.Dispose();
}
}
While slightly involved, this class works extremely well. We can get a new instance of Swimmer
using the static GetOne()
method – and only that way, since the constructor is private.
After we are done with the object, we can dispose it using either the Dispose()
method, or by having it cleaned up automatically in the finalizer. Either way the object eventually returns to our pool and can be used again.
On top of that, the entire process is completely thread-safe.
Abstracting a generic base class
The above code has one issue: If we want to pool more than one kind of object into separate pools, we need to duplicate this same code for each of them.
That is hardly ideal.
In the best case, we can create a shared base class that handles as much – if not all – of this for us.
The tricky bit is that we need to know the type we are going to pool – in our base class. Without that information we can not call the generic pooling methods with the correct parameters.
Clearly we need to use a generic type parameter for this:
class PooledObject<TSelf> : IDisposable
{
public static TSelf GetOne()
{
return StaticObjectPool.PopOrNew<TSelf>();
}
public void Dispose()
{
StaticObjectPool.Push((TSelf)this);
}
~PooledObject()
{
this.Dispose();
}
}
Note that I stripped out the thread-safety and multiple-dispose protection for brevity.
The intended use of this class would be as follows:
class Swimmer : PooledObject<Swimmer>
{
// all pooling is handled by base class
}
// made possible by static method inheritance
var swimmer = Swimmer.GetOne();
// note how the generic type does not need to be specified again
If we try to compile this class however, we get two errors however – one for each usage of the TSelf
type parameter.
Default constructor
The first is easy to fix: We can only use the PopOrNew()
method with types that have a default constructor. To enforce this, and make the line compile, we can add the appropriate constraint to our generic type parameter:
class PooledObject<TSelf> : IDisposable
where TSelf : new()
{ }
class Swimmer : PooledObject<Swimmer>
{
public Swimmer()
{
// default constructor
}
}
Note that we cannot use a private constructor anymore. That means that if we only use the constructor to create objects – instead of the GetOne()
method, our pool will get larger and larger. We effectively have created a memory leak.
There are a number of different ways to solve that problem however.
We could for example make sure that only objects created in GetOne()
can be returned to the pool by setting an appropriate flag.
Alternatively, we could limit our pool to an arbitrary size and not accept more objects if the pool is full.
Self referencing generics
The second error is more interesting:
The compiler does not let us cast PooledObject<TSelf>
to TSelf
because it has no guarantees that the cast is in fact valid.
We could avoid the problem by using the as
operator instead, and then checking for ‘null’ – or assuming that the cast succeeded.
However, there is an even nicer solution:
We can enforce the inheritance properly:
class PooledObject<TSelf> : IDisposable
where TSelf : PooledObject<TSelf>, new()
{ }
This makes sure that the generic type parameter is truly a self-reference.
Or does it?
No. Unfortunately, it does not.
Certainly, there are no compile errors any more, and in fact everything seems to work just fine if use our Swimmer
:
class Swimmer : PooledObject<Swimmer>
{
}
However, what if we try to break it by giving a wrong type parameter?
class Pirate : PooledObject<Swimmer>
{
}
This compiles just fine. But what behaviour do we expect here?
The class clearly cannot be pooled as Pirate
, since our generic base-class has no reference to that type.
However, it can also not be pooled as Swimmer
, since a Pirate
– in this case – is not a Swimmer
, despite sharing the same base class.
Unsurprisingly then, the following will throw an exception:
var p = new Pirate();
p.Dispose();
It tries to cast the pirate to a swimmer, which fails.
On a side note the following code will run fine:
var p = Pirate.GetOne();
p.Dispose();
The reason is that GetOne()
in this case actually returns a Swimmer
. After all, Pirate
specifies Swimmer
as type argument for the base class.
Enforcing self referencing generics
Unfortunately, there is no way to truly enforce the kind of constraint we would like at compile time.
Short of ignoring this problem and letting it throw an exception if the class is used incorrectly, the only option that remains is to make sure we never perform the invalid cast.
We could again look at the as
operator, but instead I will look back to the problem of a public default constructor we encountered above.
One of the solutions to that problem I proposed was keeping a flag so that we can check where the object came from. In fact, if we look back to the very first code snippet above, we see that we already have a flag that we use to make sure an object is never disposed of more than once.
We can use that same flag to make sure manually created objects can never be pooled by simply changing the flag’s default value.
Here is the full code of the updated class:
class PooledObject<TSelf> : IDisposable
where TSelf : PooledObject<TSelf>, new()
{
// flipped values to make even uninitialised
// objects behave correctly when finalizer is run
private const int disposedFalse = 1;
private const int disposedTrue = 0;
// changed default flag value
private int disposed = disposedTrue;
public static TSelf GetOne()
{
var obj = StaticObjectPool.PopOrNew<TSelf>();
obj.disposed = disposedFalse;
return obj;
}
public void Dispose()
{
if(Interlocked.Exchange(
ref this.disposed, disposedTrue
) == disposedTrue)
return;
StaticObjectPool.Push((TSelf)this);
}
~PooledObject()
{
this.Dispose();
}
}
This small change is all that is needed to solve both problems.
Now the Dispose()
of our Pirate
will always return without attempting to cast and pool the object.
One remaining caveat
With that resolved, there is really only a single problem that remains.
Imagine that we change our Pirate
class to inherit from Swimmer
:
class Swimmer : PooledObject<Swimmer>
{ }
class Pirate : Swimmer
{ }
While we can create instances of Pirate
, there is now no way to pool our pirates together with the other swimmers, since there is no way for the flag to be set appropriately.
Unless we want to increase the complexity of our code even further – and by doing so make it much less elegant – this is a trade-off we will have to make.
Overall, it really is not that bad either. At least we have the guarantee that Swimmer.GetOne()
will always return a pure Swimmer
, and never a Pirate
instead.
Conclusion
In this post we probably went about as far as we can with the goal of automating object pooling.
I hope that this exploration of generics – and self referencing ones at that – has been interesting. Feel free to drop a comment if anything is unclear, or if you have alternative solutions to the questions above.
Also make sure to let me know of any other uses for self referencing generics you have come across – after all, it should not come as a surprise at this point that I am a big fan of generics.
Enjoy the pixels!