创建一个通用的类池,您可以提供通用参数并获得使用该参数的通用对象。

3

目标

我有一个通用类 GenericClass<T>,想要对实例进行池化。
我希望看到是否能够获得以下语法:

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

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

我的理解是,泛型不支持这样的语法,因此无法实现。但我很想知道其他人怎么看。


我的思路

从我的理解来看,类型GenericClass<string>GenericClass<int>在类型系统的角度并没有关联。

但是,我意识到我可以做到接近,例如:

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

然后让GenericPool只存储一个Dictionary<Type, ObjectPool<object>>
我想知道能否避免这种做法。当作为调用者时,我只改变泛型类型参数,不想每次都要指定泛型类型。我还想在编译时强制执行所有进入我的GenericObjectPool<T>的对象都是设置的泛型类型(T<>)。

我认为问题在于无法将泛型类型参数视为本身是泛型的。如果可以这样做(我已经可以了吗?),那么可能以下内容就可以工作:

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>;
    }
}

问题

我能获得我想要的语法吗(在顶部)?如果不行,我可以得到多接近的语法?


我在阅读您的帖子时有些迷失(现在是午餐时间),所以如果我说错了,请原谅,但您是否考虑过反射? - Samuel Slade
2
反射是我最先想到的,但它的问题可能在于它不是强类型的(无法以George想要的方式表达类型约束)- 它需要将结果转换为TGeneric <TGenericParam> - Cristian Lupascu
2个回答

5
你想要实现的解决方案/语法并不可行,因为你不能将未指定类型参数的通用类型作为另一个通用类型的类型参数使用。
然而,你可以采用以下方法实现类似的结果:
1. 为类池创建一个基类,需要你提供完整的通用类型。 2. 创建特定通用类型的派生类。
类似于这样:
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>>();
    }
}

然后使用方式如下:

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>();

这种解决方案的缺点是,您需要为每个要池化的泛型类型创建一个池类,因此您可能会有很多从ObjectPool派生的classNamePool类。
为了使其可用,所有真正的代码都需要在ObjectPool类中,只有提供泛型参数的代码留在派生类中。

是的,我有点意识到这是不可能的。正如你所指出的那样,这是一个很好的替代方案,尽管它也有其缺点。 - George Duckett
@GeorgeDuckett: 确实如此。但这种解决方案的主要好处是:在创建派生类和池实例后,使用方式如期望的那样 - 在调用“Get”时不需要指定GenericClass <int>。您甚至可以创建一个动态模板来生成该派生类,因为它始终保持不变。 - Daniel Hilgarth
没错,这几乎是我理想中想要的。您使用模板的建议也很好。可以使用 T4 来实现一些高级功能,给它一个泛型类名称列表,然后它会生成用于这些泛型类池的 C# 代码。 - George Duckett

1
我希望分享我的对象池类。它们与其他发布的代码具有相似的API,但在我完全主观的意见中更加发展和灵活。
单类型对象池:
/// <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
}

接下来是多类型池。
使用这个类和自己使用多个Pool<T>没有区别。但在某些情况下,使用这个类可以使代码看起来更整洁,即消除if/else (type == foo)块。
/// <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;
    }
}

就“不必每次访问池时指定类型参数”而言,我常常为经常使用的特定类型声明一个具体的池。
public class GameObjectPool : Pool<GameObject>
{   
   public GameObjectPool(int initialCapacity)
      :base(initialCapacity, CreateObject, ResetObject)
   {
   }

   private GameObject CreateObject()
   { ... }

   private GameObject ResetObject()
   { ... }
}

然后你的代码是...

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

可以翻译为:

能够变成...

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

另一个选项是...
using GameObjectPool=MyRootnamespace.Pool<GameObject>

如果您有大量对池的引用,但它们都在同一个代码文件中,则可以工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接