我应该假设每个拥有的实例都实现IDisposable吗



我正在做我的小游戏项目,作为学习和练习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的状态机。我们可以有MainMenuStateLoadingStateSinglePlayingState。但是添加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()

如果你感兴趣的话,我会在这里谈论更多。

最新更新