Is there a way to prevent method from storing passed argument? I stumbled across this question while considering object pooling.
Let's consider given situation:
public class MyClass
{
private BigObject myBigObject;
public MyClass()
{
myBigObject = BigObjectsPool.Borrow();
//initialize myBigObject with some data
}
public void ActUpon(ClientClass clientClassInstance)
{
clientClassInstance.DoStuff(myBigObject);
}
public void Terminate() //MyClass object is no longer needed
{
BigObjectsPool.Return(myBigObject);
}
}
public abstract class ClientClass
{
public abstract void DoStuff(BigObject bigObject);
}
public class PotentiallyDangerousClientClass : ClientClass
{
private BigObject cachedBigObject;
private string cachedString
public override void DoStuff(BigObject bigObject)
{
cachedString = bigObject.SomeString; //this is ok
cachedBigObject = bigObject; //this will cause problems
}
}
As you can see, storing bigObject in DoStuff method is dangerous, because in future another instance of MyClass can borrow the same BigObject instance and change it without PotentiallyDangerousClientClass knowledge. Is there a way to ensure safety when passing pooled objects as arguments?
Obvious soultion would be to never expose pooled object and use it only for internal class logic, or expose only it's immutable fields, but it heavily reduces pooling usefulness. Exposing copy of BigObject would render whole pooling obsolete, because I would no longer benefit from reduced allocation.
You can create a thin wrapper around the object that exposes it's members, but only until you release it:
public class TempBigObject : IDisposable
{
private BigObject bigObject;
public TempBigObject(BigObject bigObject)
{
this.bigObject = bigObject;
}
public string SomeString => bigObject.SomeString;
public void Dispose()
{
bigObject = null;
}
}
If you want to, you can change all of the members, such as SomeString
, to throw some more meaningful exception (such as an object disposed exception) when used after disposal, rather than letting null reference exceptions happen.
You can then change ActUpon
to something like:
public void ActUpon(ClientClass clientClassInstance)
{
using TempBigObject temp = new TempBigObject(myBigObject);
clientClassInstance.DoStuff(temp);
}
This would of course require the clients to use the wrapper type and not the actual type.