我正在从事我的小游戏项目,作为学习和实践C#的一种方式,但是遇到了设计问题。假设我们有以下几类:
interface IGameState
{
//Updates the state and returns next active state
//(Probably itself or a new one)
IGameState Tick();
}
class Game
{
public Game(IGameState initialState)
{
activeState = initialState;
}
public void Tick()
{
activeState = activeState.Tick();
}
IGameState activeState;
}
游戏基本上是GameStates的状态机。我们可以有MainMenuState
,LoadingState
或SinglePlayingState
。但是添加MultiplayerState
(代表玩多人游戏)需要一个套接字才能连接到服务器:
class MultiplayerState : IGameState, IDisposable
{
public IGameState Tick()
{
//Game logic...
//Communicate with the server using the Socket
//Game logic...
//Render the game
return this;//Or something else if the player quits
}
public void Dispose()
{
server.Dispose();
}
//Long-living, cannot be in method-scope
Socket server;//Or similar network resource
}
好,这是我的问题,我无法将其传递给Game
,因为它不知道应该处理它,并且调用代码无法轻易知道何时游戏不再需要它。到目前为止,此类设计几乎完全是我已经实现的设计,将IDisposable
添加到IGameState
会很好,但是我认为它不是一个好的设计选择,毕竟不是所有的{{1} }个有资源。此外,在任何活动的IGameState
都可以返回新状态的意义上,该状态机是动态的。因此,IGameState
确实不知道哪些是一次性的,哪些是一次性的,因此只能对所有部件进行试铸。
所以这让我问了几个问题:
Game
),我是否应该始终假设它可以是initialState
? (可能不是)IDisposable
实例,是否应该通过转换为不实现IDisposable
的基础来放弃其所有权? (可能没有)据此我发现IDisposable
感觉像是一个非常独特的界面,具有显着的有损(*)语义-它关心自己的生命周期。这似乎与提供有保证但不确定的内存管理的GC本身的想法直接冲突。我来自C ++,所以确实感觉它试图实现RAII概念,但是一旦有0个引用,就手动调用Dispose(destructor)。我并不是说这完全是C#上的语,就像我是否缺少某些语言功能一样?还是为此特定的C#模式?我知道这里有IDisposable
,但这只是方法范围。接下来,有一些终结器可以确保调用Dispose,但仍不确定,还有其他什么吗?也许像C ++'using
这样的自动引用计数?
正如我已经说过的,可以通过不同的设计解决上述示例(但我认为不应该这样做),但不能回答不可能的情况,因此请不要过多关注。理想情况下,我希望看到解决类似问题的一般模式。
(*)对不起,也许不是一个好词。但是我的意思是,很多接口都表示一种行为,并且如果类实现了该接口,它只会说“嘿,我也可以也执行这些操作,但是如果您忽略我的那一部分,我仍然可以正常工作”。忘记shared_ptr
并不是无损。
在question之后,我发现IDisposable可以按组成传播,也可以通过继承传播。这对我来说似乎是正确的,需要更多键入,但是可以。也正是IDisposable
被感染的方式。但是在我的MultiplayerState
类示例中,它也想向上游传播,这感觉不对。
最后一个问题可能是,是否甚至应该有任何有损接口,例如,如果它是完成工作的正确工具,那是什么?还是我应该知道的其他常用的有损接口?
答案 0 :(得分:0)
您所有的问题都是有效的讨论;但是,当涉及IDisposable
时,如果将其传递给某个类型,则处于未知状态,不知道该类型是否可以正确处理它。因此,通常,一次性类型的原始所有者/初始化者应始终负责处理。
因此,在您的情况下,实例化MultiplayerState
的人也应负责处置它。如果必须实例化它,然后将其传递给GameState
并在以后进行处理,则应要求MultiplayerState
的原始所有者以某种方式对其进行跟踪并进行适当处理。
此外,在实现IDisposable
时,我强烈建议您也将处置添加到类的析构函数中。如果不正确处理或处置不正确的一次性类型,这是一种故障保险。
示例:
public void Dispose()
{
server.Dispose();
GC.SuppressFinalize(this);
}
~MultiplayerState() => Dispose()
如果您有兴趣,我会在here上进行更多讨论。