代码块后自动调用方法
我在游戏中添加了在设定时间间隔后可重复操作的概念。
我有一个类来管理是否可以执行给定的操作。
调用者通过调用 CanDoAction
查询是否可以执行该操作,如果可以,则执行该操作并使用 MarkActionDone
记录他们已完成该操作。
if (WorldManager.CanDoAction(playerControlComponent.CreateBulletActionId))
{
// Do the action
WorldManager.MarkActionDone(playerControlComponent.CreateBulletActionId);
}
显然,这可能容易出错,因为您可能忘记调用 MarkActionDone
,或者可能忘记调用 CanDoAction
进行检查。
理想情况下,我希望保持类似的界面,而不必传递 Action
或类似的内容,因为我在 Xbox 上运行,并且希望避免传递操作并调用它们。特别是因为必须涉及大量闭包,因为操作通常依赖于周围的代码。
我正在考虑以某种方式(ab)使用 IDisposeable
接口,因为这将确保最后可以调用 MarkActionDone
,但是我不认为我可以跳过如果 CanDoAction
为 false,则使用块。
有什么想法吗?
I'm adding the notion of actions that are repeatable after a set time interval in my game.
I have a class that manages whether a given action can be performed.
Callers query whether they can perform the action by calling CanDoAction
, then if so, perform the action and record that they've done the action with MarkActionDone
.
if (WorldManager.CanDoAction(playerControlComponent.CreateBulletActionId))
{
// Do the action
WorldManager.MarkActionDone(playerControlComponent.CreateBulletActionId);
}
Obviously this could be error prone, as you could forget to call MarkActionDone
, or possibly you could forget to call CanDoAction
to check.
Ideally I want to keep a similar interface, not having to pass around Action
's or anything like that as I'm running on the Xbox and would prefer to avoid passing actions around and invoking them. Particularly as there would have to be a lot of closures involved as the actions are typically dependent on surrounding code.
I was thinking of somehow (ab)using the IDisposeable
interface, as that would ensure the MarkActionDone
could be called at the end, however i don't think i can skip the using block if CanDoAction
would be false.
Any ideas?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我的首选方法是使用委托模式将此逻辑保留为 WorldManager 的实现细节(因为它定义了有关是否可以执行操作的规则):
这似乎最适合 SOLID 原则,因为它避免了任何其他类必须“了解”
WorldManager
的“CanDoAction”、“MarkActionDone”实现细节。更新
使用 AOP 框架,例如 PostSharp,可能是一个不错的选择,以确保这方面以干净的方式添加到所有必要的代码块中。
My preferred approach would be to keep this logic as an implementation detail of WorldManager (since that defines the rules about whether an action can be performed), using a delegate pattern:
This seems to fit best with SOLID principals, since it avoids any other class having to 'know' about the 'CanDoAction', 'MarkActionDone' implementation detail of
WorldManager
.Update
Using an AOP framework, such as PostSharp, may be a good choice to ensure this aspect is added to all necessary code blocks in a clean manner.
如果你想最小化 GC 压力,我建议使用接口而不是委托。如果您使用
IDisposable
,则无法避免调用Dispose
,但您可以让 IDisposable 实现使用一个标志来指示Dispose
方法不应该做任何事情。除了委托具有一些内置语言支持这一事实之外,它们实际上没有什么是接口不能做的,但接口比委托有两个优点:第二种方法需要注意的是:尽管避免 GC 压力是一件好事,但第二种方法最终可能会在运行时创建大量类型。在大多数情况下,创建的类型数量是有限的,但在某些情况下,它可能会无限制地增加。我不确定是否有任何方便的方法来确定在静态分析就足够的情况下程序可以生成的完整类型集(在一般情况下,静态分析不够,确定是否有任何特定的类型)生成的运行时类型相当于停止问题,但正如许多程序实际上可以静态地确定为始终停止或永不停止一样,我希望在实践中人们通常可以识别一组封闭的类型程序可以在运行时生成)。
编辑
上面第#2点中的格式被搞乱了。这是解释,已清理。
定义一个类型
ConditionalCleaner; :IDisposable
,它保存一个T
的实例和一个Action
(两者都在构造函数中提供 - 可能与Action;
作为第一个参数)。在IDisposable.Dispose()
方法中,如果Action
为非 null,则在T
上调用它。在SkipDispose()
方法中,将Action
清空。为了方便起见,您可能还需要类似地定义ConditionalCleaner: IDisposable
(也许还有三参数和四参数版本),并且您可能希望使用泛型定义静态类 ConditionalCleanerCreate
、Create
等方法(因此可以说例如using (var cc = ConditionalCleaner.Create(Console.WriteLine, "ABCDEF") {...}
或ConditionalCleaner.Create((x) => {Console.WriteLine(x);}, "ABCDEF" )
在 using 块退出时执行指定的操作,如果使用 Lambda 表达式,则最大的要求是确保 Lambda 表达式不会关闭任何局部变量或参数。调用函数;调用函数想要传递给 lambda 表达式的任何内容都必须是其显式参数,否则系统将定义一个类对象来保存任何封闭变量,以及指向它的新委托。If you want to minimize GC pressure, I would suggest using interfaces rather than delegates. If you use
IDisposable
, you can't avoid havingDispose
called, but you could have the IDisposable implementation use a flag to indicate that theDispose
method shouldn't do anything. Beyond the fact that delegates have some built-in language support, there isn't really anything they can do that interfaces cannot, but interfaces offer two advantages over delegates:One caveat with the second approach: Although avoiding GC pressure is a good thing, the second approach may end up creating a very large number of types at run-time. The number of types created will in most cases be bounded, but there are circumstances where it could increase without bound. I'm not sure if there would any convenient way to determine the full set of types that could be produced by a program in cases where static analysis would be sufficient (in the general case, where static analysis does not suffice, determining whether any particular run-time type would get produced would be equivalent to the Halting Problem, but just as many programs can in practice be statically determined to always halt or never halt, I would expect that in practice one could often identify a closed set of types that a program could produce at run-time).
Edit
The formatting in point #2 above was messed up. Here's the explanation, cleaned up.
Define a type
ConditionalCleaner<T> : IDisposable
, which holds an instance ofT
and anAction<T>
(both supplied in the constructor--probably with theAction<T>
as the first parameter). In theIDisposable.Dispose()
method, if theAction<T>
is non-null, invoke it on theT
. In aSkipDispose()
method, null out theAction<T>
. For convenience, you may want to also defineConditionalCleaner<T,U>: IDisposable
similarly (perhaps three- and four-argument versions as well), and you may want to define a static class ConditionalCleaner with genericCreate<T>
,Create<T,U>
, etc. methods (so one could say e.g.using (var cc = ConditionalCleaner.Create(Console.WriteLine, "ABCDEF") {...}
orConditionalCleaner.Create((x) => {Console.WriteLine(x);}, "ABCDEF")
to have the indicated action performed when the using block exits. The biggest requirement if one uses a Lambda expression is to ensure that the lambda expression doesn't close over any local variables or parameters from the calling function; anything the calling function wants to pass to the lambda expression must be an explicit parameter thereof. Otherwise the system will define a class object to hold any closed-over variables, as well as a new delegate pointing to it.