Creating a generic class pool where you give the generic parameter and get a generic object that used that parameter

StackOverflow https://stackoverflow.com/questions/8927133

문제

Goal

I have a generic class GenericClass<T> and I want to pool instances.
I'm interested in seeing if I can get the syntax:

MyGenericPool = new GenericPool<GenericClass>();
// Or maybe it's MyGenericPool = new GenericPool<GenericClass<>>();

GenericClass<TGenericParam> GenericClassInstance =
    MyGenericPool.Get<TGenericParam>();

(My understanding of generics says, no I can't, don't be silly the syntax doesn't exist / wouldn't work, but I'm intested in what others think).


Showing my workings

I'm a bit doubtful as from my understanding the types GenericClass<string> and GenericClass<int> aren't really related from the type system's point of view.

Now, I realise that I can get close, i.e.:

GenericClass<TGenericParam> GenericClassInstance =
    GenericPool.Get<GenericClass<TGenericParam>>();

and then have the GenericPool just store a Dictionary<Type, ObjectPool<object>> somewhere.
I'm interested in seeing if I can avoid having to do that. I don't want to have to specify the generic type every time when, as the caller, i'm only changing the generic type parameter. I'd also like to be able to enforce (compile time) that all objects going into my GenericObjectPool<T> are of a set generic type (T<>).


I think the problem stems from not being able to treat a generic type parameter as being generic its self. If I could do that (can I already??) then maybe something like the below might work:

public class GenericClassPool<TGeneric> where TGeneric : class<>
{
    private readonly Dictionary<Type, object> objectPools = new Dictionary<Type, object>();


    private void EnsureObjectPoolExists<TGenericParam>()
    {
        if (!objectPools.ContainsKey(typeof(TGenericParam)))
        {
            objectPools.Add(typeof(TGenericParam), new ObjectPool<TGeneric<TGenericParam>>(() => Activator.CreateInstance(typeof(TGeneric<TGenericParam>)) as TGeneric<TGenericParam>));
        }
    }

    private ObjectPool<TGeneric<TGenericParam>> GetPool<TGenericParam>()
    {
        EnsureObjectPoolExists<TGenericParam>();
        return (objectPools[typeof(TGenericParam)] as ObjectPool<TGeneric<TGenericParam>>);
    }

    public void Add<TTypeParam>(TGeneric<TGenericParam> obj)
    {
        EnsureObjectPoolExists<TTypeParam>();

        GetPool<TGenericParam>().Add(obj);
    }

    public TGeneric<TGenericParam> Get<TGenericParam>()
    {
        return GetPool<TGenericParam>().Get() as TGeneric<TGenericParam>;
    }
}

Question

Can I get the syntax I want (at the top)? If not, how close can I get?

도움이 되었습니까?

해결책

The solution / syntax you are trying to achieve doesn't work that way, because you can't use a generic type without its type parameters as the type parameter to another generic type.

However, you could achieve similar results with the following approach:

  1. Create a base class for the class pool that requires you to supply the complete generic type
  2. Create a derived class for the specific generic type

Something like that:

public class ObjectPool
{
    Dictionary<Type, object> _objectPool = new Dictionary<Type, object>();

    public void Add<TKey, TValue>(TValue value)
    {
        _objectPool.Add(typeof(TKey), value);
    }

    public TValue Get<TKey, TValue>() where TValue : class
    {
        object value;
        if(_objectPool.TryGetValue(typeof(TKey), out value))
            return value as TValue;
        return null;
    }
}

public class GenericClassPool : ObjectPool
{
    public void Add<TGenericParam>(GenericClass<TGenericParam> obj)
    {
        Add<TGenericParam, GenericClass<TGenericParam>>(obj);
    }

    public GenericClass<TGenericParam> Get<TGenericParam>()
    {
        return Get<TGenericParam, GenericClass<TGenericParam>>();
    }
}

Usage would then be like this:

var pool = new GenericClassPool();
pool.Add(new GenericClass<string> { Property = "String" });
pool.Add(new GenericClass<int> { Property  = 0 });

GenericClass<string> firstObject = pool.Get<string>();
GenericClass<int> secondObject = pool.Get<int>();

The draw back of this solution is that you would need to create one pool class for each generic type you want to pool, so you potentially will have a lot of <className>Pool classes deriving from ObjectPool.
To make this usable, all real code needs to be in the ObjectPool class and only code that supplies the generic parameters remains in the derived classes.

다른 팁

I'd like to share my own pool classes. They have a similar API to the other code posted but are a bit more developed and flexible, in my totally biased opinion.

Single type object pool:

/// <summary>
/// Allows code to operate on a Pool<T> without casting to an explicit generic type.
/// </summary>
public interface IPool
{
    Type ItemType { get; }
    void Return(object item);
}

/// <summary>
/// A pool of items of the same type.
/// 
/// Items are taken and then later returned to the pool (generally for reference types) to avoid allocations and
/// the resulting garbage generation.
/// 
/// Any pool must have a way to 'reset' returned items to a canonical state.
/// This class delegates that work to the allocator (literally, with a delegate) who probably knows more about the type being pooled.
/// </summary>    
public class Pool<T> : IPool
{
    public delegate T Create();
    public readonly Create HandleCreate;

    public delegate void Reset(ref T item);
    public readonly Reset HandleReset;

    private readonly List<T> _in;

#if !SHIPPING
    private readonly List<T> _out;
#endif

    public Type ItemType
    {
        get
        {
            return typeof (T);   
        }            
    }

    public Pool(int initialCapacity, Create createMethod, Reset resetMethod)
    {
        HandleCreate = createMethod;
        HandleReset = resetMethod;

        _in = new List<T>(initialCapacity);            
        for (var i = 0; i < initialCapacity; i++)
        {
            _in.Add(HandleCreate());
        }
#if !SHIPPING
        _out = new List<T>();            
#endif
    }

    public T Get()
    {
        if (_in.Count == 0)
        {
            _in.Add(HandleCreate());
        }

        var item = _in.PopLast();
#if !SHIPPING
        _out.Add(item);
#endif
        return item;
    }

    public void Return( T item )
    {
        HandleReset(ref item);
#if !SHIPPING
        Debug.Assert(!_in.Contains(item), "Returning an Item we already have.");
        Debug.Assert(_out.Contains(item), "Returning an Item we never gave out.");
        _out.Remove(item);
#endif
        _in.Add(item);
    }

    public void Return( object item )
    {
        Return((T) item);
    }

#if !SHIPPING
    public void Validate()
    {
        Debug.Assert(_out.Count == 0, "An Item was not returned.");
    }
#endif
}

Next, a multi-type pool.

There is no difference between using this class or using multiple Pool<T> yourself. But in some situations using this class will make code look cleaner, ie. eliminating if/else (type == foo) blocks.

/// <summary>
/// Represents a collection of pools for one or more object types.
/// </summary>
public class Pooler
{
    private readonly List<IPool> _pools;

    public Pooler()
    {
        _pools = new List<IPool>();
    }

    public void DefineType<T>(int initialCapacity, Pool<T>.Create createHandler, Pool<T>.Reset resetHandler)
    {
        var p = new Pool<T>(initialCapacity, createHandler, resetHandler);
        _pools.Add(p);
    }

    public T Get<T>()
    {
        var p = GetPool(typeof (T));
        if (p == null)
            throw new Exception(string.Format("Pooler.Get<{0}>() failed; there is no pool for that type.", typeof(T)));

        return ((Pool<T>)p).Get();
    }

    public void Return(object item)
    {
        var p = GetPool(item.GetType());
        if (p == null)
            throw new Exception(string.Format("Pooler.Get<{0}>() failed; there is no pool for that type.", item.GetType()));

        p.Return(item);            
    }

    private IPool GetPool(Type itemType)
    {
        foreach (var p in _pools)
        {
            if (p.ItemType == itemType)
            {
                return p;
            }
        }

        return null;
    }
}

As far as 'not having to specify the type parameter every time you access the pool', I often declare a concrete pool for a specific type that is frequently used.

public class GameObjectPool : Pool<GameObject>
{   
   public GameObjectPool(int initialCapacity)
      :base(initialCapacity, CreateObject, ResetObject)
   {
   }

   private GameObject CreateObject()
   { ... }

   private GameObject ResetObject()
   { ... }
}

Then your code which was...

_pool = new Pool<GameObject>(10);
var obj = _pool.Get<GameObject>();

Can become...

_pool = new GameObjectPool(10);
var obj = _pool.Get();

Another option is...

using GameObjectPool=MyRootnamespace.Pool<GameObject>

Which can work if you have a ton of references to the pool, but they are all in the same code file.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top