我正在做我的小游戏项目,作为学习和练习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
中,但我认为这不是一个好的设计选择,毕竟不是所有的IGameState
都有资源。此外,在任何活动的IGameState
都可以返回新状态的意义上,该状态机是动态的。所以Game
真的不知道哪些是一次性的,哪些是,所以它只能测试所有的东西。
所以这让我问了几个问题:
- 如果我有一个类声称对非密封类型的参数(例如Game的ctor中的
initialState
(拥有所有权,我应该一直假设它可以是IDisposable
吗?(可能不会( - 如果我有一个
IDisposable
实例,我是否应该通过强制转换到不实现IDisposable
的基来放弃它的所有权?(可能没有(
我从中得出结论,IDisposable
感觉像是一个非常独特的接口,具有显著的有损(*(语义——它关心自己的生命周期。这似乎和GC本身提供有保证但不确定的内存管理的想法直接冲突。我来自C++背景,所以它真的感觉像是试图实现RAII概念,但只要有0个引用,就会手动调用Dispose(析构函数(。我的意思根本不是对C#的咆哮,更像是我错过了一些语言功能吗?或者可能是C#特有的模式?我知道有using
,但这只是方法范围。接下来是终结器,它可以确保对Dispose的调用,但仍然是不确定的,还有其他东西吗?也许是像C++的shared_ptr
那样的自动引用计数?
正如我所说,上面的例子可以通过不同的设计来解决(但我认为不应该(,但没有回答可能不可能的情况,所以请不要过于关注它。理想情况下,我希望看到解决类似问题的一般模式。
(*(对不起,也许不是一个好词。但我的意思是,很多接口都表达了一种行为,如果类实现了所述接口,它只会说"嘿,我也可以做这些事情,但如果你忽略了我的这一部分,我仍然可以正常工作"。忘记IDisposable
不是无损。我发现了以下问题,它表明IDisposable通过组合传播,或者它可以通过继承传播。这在我看来是正确的,需要更多的打字,但没关系。MultiplayerState
就是这样感染的。但在我的Game
类示例中,它也想向上游传播,这感觉不太好。
最后一个问题可能是,是否应该有任何有损接口,比如它是否是适合工作的工具,在这种情况下是什么?或者我知道应该了解其他常用的有损接口吗?
您的所有问题都是有效的讨论;然而,当涉及到IDisposable
时,如果将其传递给某个类型,则处于未知状态,不知道该类型是否会正确处理它。出于这个原因,通常,可丢弃类型的原始所有者/初始值设定项应该始终负责处理。
因此,在您的情况下,实例化MultiplayerState
的人也有责任处理它。如果必须实例化它,然后将它传递给GameState
,然后稍后处理它,那么应该要求MultiplayerState
的原始所有者以某种方式跟踪它并正确处理它。
此外,在实现IDisposable
时,我强烈建议也将disposition添加到类的析构函数中。这是一个故障保险,以防一次性类型没有得到正确处理或正确实施。
示例:
public void Dispose()
{
server.Dispose();
GC.SuppressFinalize(this);
}
~MultiplayerState() => Dispose()
如果你感兴趣的话,我会在这里谈论更多。