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

Should I assume that every owned instance implements IDisposable?

我正在开发我的小游戏项目,作为学习和练习 C# 的一种方式,我遇到了一个设计问题。假设我们有以下一组 classes:

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;
}

Game 基本上是 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,因为它不知道应该处理它,并且调用代码无法轻易知道游戏何时不再需要它。这个 class 设计几乎正是我目前实现的,我可以将 IDisposable 添加到 IGameState 但我不认为它是一个好的设计选择,毕竟不是所有IGameState有资源。此外,从任何活动 IGameState 都可以 return 新状态的意义上说,该状态机是动态的。所以 Game 真的不知道哪些是一次性的,哪些是一次性的,所以它必须对所有东西进行测试。

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

我从中了解到 IDisposable 感觉像是一个非常独特的界面,具有重要的 lossy(*) 语义 - 它关心自己的生命周期。这似乎与 GC 本身的想法直接冲突,GC 本身提供有保证但不确定的内存管理。我来自 C++ 背景,所以它真的感觉它试图实现 RAII 概念,但只要有 0 个引用,就会手动调用 Dispose(destructor)。我并不是说这是对 C# 的咆哮,更像是我是否缺少某些语言功能?或者也许是 C# 特定的模式?我知道有 using 但这只是方法范围。接下来是终结器,它可以确保调用 Dispose 但仍然不确定,还有什么吗?也许自动引用计数像 C++' shared_ptr?

正如我所说,上面的例子可以通过不同的设计来解决(但我认为不应该),但没有回答可能无法解决的情况,所以请不要关注它太多了。理想情况下,我希望看到解决类似问题的一般模式。

(*) 对不起,也许不是一个好词。但我的意思是,很多接口都表达了一种行为,如果 class 实现了所述接口,它就表示 "Hey, I can also do these things but if you ignore that part of me I still work just fine"。忘记 IDisposable 并不是 无损 。 我发现以下 question 表明 IDisposable 按组合传播,或者它可以通过继承传播。这对我来说似乎是正确的,需要更多的打字,但没关系。这也正是 MultiplayerState 被感染的原因。但在我使用 Game class 的示例中,它还想向上游传播,但感觉不对。

最后一个问题可能是是否应该有任何 有损 接口,比如它是否是完成工作的正确工具,在那种情况下是什么?或者还有其他我知道的常用 lossy 接口吗?

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

所以在你的情况下,实例化 MultiplayerState 的人也负责处理它。如果你必须实例化它,然后将它传递给 GameState 并稍后处理它,那么应该要求 MultiplayerState 的原始所有者以某种方式跟踪它并正确处理它。

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

示例:

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

 ~MultiplayerState() => Dispose()

如果你有兴趣,我再谈这个