为所有对象创建一个委托,而不是为每个对象创建一个委托
Posted
技术标签:
【中文标题】为所有对象创建一个委托,而不是为每个对象创建一个委托【英文标题】:Create a single delegate for all objects instead of one delegate per object 【发布时间】:2011-05-07 22:14:04 【问题描述】:我有一个对象池,每当我对池中的对象调用 Free() 时,我都需要调用委托方法 OnFree()。
Free() 是在外部创建的,并在创建池时设置在对象上。 OnFree 因对象而异,有时甚至为 null。
池中的对象继承自 Poolable 类。
class Poolable
public Action Free();
public Action OnFree();
目前我通过这样做在继承类中创建 OnFree:
class Light
public Light()
// Create method to be called when Free() is called on this light.
OnFree = () =>
DoStuffHere();
;
但是,这将为每个灯光创建一个单独的委托,这会浪费大量内存,尤其是当池中有数万个对象时。呃,每次调用这个构造函数时都会创建一个新的委托,对吧?
什么是允许对象创建自己的 OnFree() 委托的好方法,以便每个对象类型只有一个委托,而不是每个实例一个委托?
我当然能想到一种方法,但我希望有人能想到一种“好”的方法——一种易于维护的方法。
编辑:我可以在基类中将 OnFree() 委托设为静态,以便它以某种方式对每个继承的类型都是静态的吗?
编辑:澄清如何使用 Pool,以及为什么 Free()
是委托,而不是虚拟方法。如果您能想到更好的方法,请告诉我。
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;
[...]
【问题讨论】:
我试图让问题保持简单,但我发现很难问清楚 -- 所以如果我遗漏了任何信息或有任何不清楚的地方,请提出问题。 IIRC,Lambda 表达式被转换为匿名委托,而匿名委托又被实现为编译器生成的类的方法。所以你只得到一种方法。唯一使用的内存是函数指针的内存。 艾蒂安:这意味着什么?这是否意味着即使我多次调用构造函数也只创建一种方法? 艾蒂安,谢谢,我希望你是对的。一些确认会很好,因为我想不出一种快速的方法来测试它。 【参考方案1】:如何保持简单:
class Poolable
public virtual void Free()
public virtual void OnFree() // naming not according to BCL std
class Light : Poolable
public override void Free()
...
您的示例显示不需要委托(通过虚拟方法)
适当的封装需要事件而不是公共委托
您的优化似乎过早了。
【讨论】:
过早地重新优化:这个池类用于保存大多数引用类型对象,因为我在为 .NET Compact CLR 开发实时应用程序时试图避免所有垃圾收集。由于此代码使用频率很高,因此我正在尝试使其合理高效。 我的示例没有显示它,但我Free()
确实需要成为委托(因为它在从 Poolable 继承的每个对象中执行相同的操作,但我无法在其中创建它poolable 因为Free()
需要访问 PoolOnFree
虚拟化似乎是正确的选择,不知道我是怎么错过的!一般来说,在为紧凑型 CLR 进行开发时,我会避免使用虚拟调用,因为它们很昂贵——我想在这种情况下这已经变成了一个坏习惯。
我对我的问题添加了一个编辑,以更好地解释我为什么让Free()
成为代表。如果您发现更好的方法,请告诉我。
我认为您可能需要的只是 Poolable 中的 MyPool 属性。但我们必须看看你如何调用 Free()。【参考方案2】:
这实际上取决于DoStuffHere()
的定义位置。如果这是一个实例方法,则将 this
隐式捕获到编译器生成的类型上;同样,可能会捕获其他任何内容(您的示例中未显示)。
在大多数正常情况下,委托实例的额外开销是最小的。避免传递创建委托的一种解决方法是使用 参数化委托(Action<SomeStateType>
,可能存储在 static
字段中),并将状态作为单独的参数提供...但是当然,那么您正在为状态创建一个对象!进行手动捕获的轻微优势是您可能(这取决于确切的代码示例)将其从 2 个(或更多)分配(1 个委托,1 个或更多捕获类)减少到 1 个分配(您的手动捕获; 代表在静态字段上)。
一种方式,另一种方式,很可能会创建某物。就个人而言,在您的分析显示它是一个瓶颈之前,我认为您应该放松一点 - 分配速度非常快,并且大多数时候对象将在 GEN-0 中收集,这非常有效。
【讨论】:
实际上,我正在尝试减少所有额外委托副本在 L1 缓存中使用的空间,因为缓存未命中在我的目标平台上异常昂贵。此外,我正在为没有 GEN-0 的 .NET 紧凑型 CLR 开发(它使用扫描和修剪垃圾收集器而不是分代方法)。不过,这是信息丰富的,+1。 @Olhovsky - 那么可能使用静态字段委托和结构上下文?【参考方案3】:如果您使用 static generic 类,您会为每种类型获得一个“实例”——这正是您所追求的。因此,使用这样的类作为特定类型委托的后台存储,并在每个 Poolable 子类的静态构造函数中初始化它们将解决您的问题。查看示例代码:
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;
【讨论】:
+1 因为这在技术上是可行的,而且您显然在此响应中付出了努力。但是,您想为每个继承自 Poolable 的类创建一个单独的池助手吗? IE。class Light : Poolable
会有一个助手,class Shadow : Poolable
也需要一个不同的助手?在这种情况下,我宁愿在 Light
类中创建一个静态字段来存储委托。我也不喜欢这种解决方案,因为这意味着可能在每次构造函数调用时重新创建委托。
其实这不太行。如果您在调用 GetOnFree() 之间创建一个新光源,那么您将创建新的委托。
@Olhovsky:不,这会为每个类型而不是每个实例创建一个委托,因为委托创建发生在类型构造函数 (static Light()
) 中,而不是实例构造函数中。当然,您可以将静态字段放入 Light 类中,但是您有责任为 Poolable 的每个子类型声明它,而 PoolHelper<T>
只需声明一次,它实现了相同的目的.恕我直言,编码工作少了很多。
ServiceGuy:哦,对不起,你是对的,我现在看到每种类型都会创建一个静态 PoolHelper——我的错!不幸的是,这个方法现在也使用了一个虚拟方法和一个委托,这是一个性能损失。以上是关于为所有对象创建一个委托,而不是为每个对象创建一个委托的主要内容,如果未能解决你的问题,请参考以下文章
为每个对象的行为创建脚本,而不是在对象脚本主体中编写所有内容