Olhovsky
Olhovsky

Reputation: 5559

Create a single delegate for all objects instead of one delegate per object

I have an object pool, and I need to call a delegate method OnFree(), whenever I call Free() on an object in the pool.

Free() is created externally and set on the object when the pool is created. OnFree differs from one object to another, and sometimes it is even null.

Objects in the pool inherit from the Poolable class.

class Poolable
{
    public Action Free();
    public Action OnFree();
}

Currently I create OnFree in the inheriting class by doing this:

class Light
{
    public Light()
    {
        // Create method to be called when Free() is called on this light.
        OnFree = () =>
        {
            DoStuffHere();
        };
    }
}

However, this will create a separate delegate for each light, which wastes a bunch of memory especially when there are tens of thousands of objects in the pool. Er, it does create a new delegate every time this constructor is called, right?

What is a good way to allow objects to create their own OnFree() delegate, so that there is only one delegate per object type, instead of one delegate per instance?

I can think of a way of course, but I'm hoping someone can think of a "good" way -- something that allows easy maintainability.


Edit: Can I make the OnFree() delegate static in the base class, so that it is static per inherited type somehow?


Edit: To clarify how Pool is used, and why Free() is a delegate, not a virtual method. Please let me know if you can think of a better way to do this.

public class Pool<T> where T : Poolable
{
    private int _liveCount;
    private T[] _pool;
    [...]
    public Pool(int capacity, Func<T> allocateFunction)
    {
        [...]
        // Fill pool with initial items:
        for (int i = 0; i < capacity; i++)
        {
            T item = _allocate();
            item.Free = () => Free(item);
            _pool[i] = item;
        }
    }

    /// <summary>
    /// Frees given object from this pool. Object is assumed to
    /// be in this pool.
    /// </summary>
    public void Free(Poolable obj)
    {
        obj.OnFree();

        _liveCount -= 1;
        [...]
    }
}

Upvotes: 2

Views: 989

Answers (3)

Henk Holterman
Henk Holterman

Reputation: 273804

How about keeping it simple:

class Poolable
{
    public virtual void Free() { }
    public virtual void OnFree() { }  // naming not according to BCL std
}

class Light : Poolable
{
    public override void Free() { }
    ...
}
  • your example shows no need for delegates (over virtual methods)
  • proper encapsulation would require events instead of public delegates
  • looks like you are optimizing prematurely.

Upvotes: 2

mthierba
mthierba

Reputation: 5667

If you use a static generic class you get one "instance" per type - which is exactly what you were after. Hence, using such a class as the backstore for your type-specific delegates, and initialize them in the static constructor of each Poolable sub-class would solve your problem. See the sample code:

public class Poolable
{
    public Action Free { get; set; }
    public Action OnFree { get { return GetOnFree(); } }

    protected virtual Action GetOnFree() { throw new NotImplementedException(); }
}

public static class PoolHelper<T> where T : Poolable
{
    public static Action OnFree { get; set; }
}

public class Light : Poolable
{
    static Light()
    {
        PoolHelper<Light>.OnFree = () => 
        {
            // Define 'OnFree' for the Light type here...
            // and do so for all other other sub-classes of Poolable
        };
    }

    protected override Action GetOnFree()
    {
        return PoolHelper<Light>.OnFree;
    }
}

Upvotes: 1

Marc Gravell
Marc Gravell

Reputation: 1064134

It actually depends on where DoStuffHere() is defined. If this is an instance method, there is an implicit capture of this onto a compiler-generated type; likewise anything else (not shown in your example) might be captured.

In most normal cases the extra overhead of a delegate instance is minimal. One workaround to avoid passing creating a delegate is to have a parameterised delegate (an Action<SomeStateType>, perhaps stored in a static field), and feed the state in as a separate parameter... but of course, then you are creating an object for the state! The slight advantage of doing a manual capture is that you are probably (it depends on the exact code sample) reducing it from 2 (or more) allocations (1 delegate, 1-or-more capture classes) to 1 allocation (your manual capture; the delegate being held on a static field).

One way of another, there is likely going to be something created. Personally, until your profiling shows it is a bottleneck, I think you should relax a bit - allocations are very fast, and most times the object will be collected in GEN-0, which is very efficient.

Upvotes: 2

Related Questions