我是否应该假设每个拥有的实例都实现IDisposable?

时间:2018-07-15 18:11:36

标签: c# design-patterns dispose

我正在从事我的小游戏项目,作为学习和实践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会很好,但是我认为它不是一个好的设计选择,毕竟不是所有的{{1} }个有资源。此外,在任何活动的IGameState都可以返回新状态的意义上,该状态机是动态的。因此,IGameState确实不知道哪些是一次性的,哪些是一次性的,因此只能对所有部件进行试铸。

所以这让我问了几个问题:

  • 如果我有一个类要求对非密封类型的参数声明所有权(例如Game的ctor中的Game),我是否应该始终假设它可以是initialState? (可能不是)
  • 如果我有一个IDisposable实例,是否应该通过转换为不实现IDisposable的基础来放弃其所有权? (可能没有)

据此我发现IDisposable感觉像是一个非常独特的界面,具有显着的有损(*)语义-它关心自己的生命周期。这似乎与提供有保证但不确定的内存管理的GC本身的想法直接冲突。我来自C ++,所以确实感觉它试图实现RAII概念,但是一旦有0个引用,就手动调用Dispose(destructor)。我并不是说这完全是C#上的语,就像我是否缺少某些语言功能一样?还是为此特定的C#模式?我知道这里有IDisposable,但这只是方法范围。接下来,有一些终结器可以确保调用Dispose,但仍不确定,还有其他什么吗?也许像C ++'using这样的自动引用计数?

正如我已经说过的,可以通过不同的设计解决上述示例(但我认为不应该这样做),但不能回答不可能的情况,因此请不要过多关注。理想情况下,我希望看到解决类似问题的一般模式。

(*)对不起,也许不是一个好词。但是我的意思是,很多接口都表示一种行为,并且如果类实现了该接口,它只会说“嘿,我也可以执行这些操作,但是如果您忽略我的那一部分,我仍然可以正常工作”。忘记shared_ptr并不是无损。 在question之后,我发现IDisposable可以按组成传播,也可以通过继承传播。这对我来说似乎是正确的,需要更多键入,但是可以。也正是IDisposable被感染的方式。但是在我的MultiplayerState类示例中,它也想向上游传播,这感觉不对。

最后一个问题可能是,是否甚至应该有任何有损接口,例如,如果它是完成工作的正确工具,那是什么?还是我应该知道的其他常用的有损接口?

1 个答案:

答案 0 :(得分:0)

您所有的问题都是有效的讨论;但是,当涉及IDisposable时,如果将其传递给某个类型,则处于未知状态,不知道该类型是否可以正确处理它。因此,通常,一次性类型的原始所有者/初始化者应始终负责处理。

因此,在您的情况下,实例化MultiplayerState的人也应负责处置它。如果必须实例化它,然后将其传递给GameState并在以后进行处理,则应要求MultiplayerState的原始所有者以某种方式对其进行跟踪并进行适当处理。

此外,在实现IDisposable时,我强烈建议您也将处置添加到类的析构函数中。如果不正确处理或处置不正确的一次性类型,这是一种故障保险。

示例:

 public void Dispose()
 {
      server.Dispose();
      GC.SuppressFinalize(this);
 }

 ~MultiplayerState() => Dispose()

如果您有兴趣,我会在here上进行更多讨论。