我是否应该假设每个拥有的实例都实现了 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 的状态机。我们可以有 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
,因为它不知道应该处理它,并且调用代码无法轻易知道游戏何时不再需要它。这个 class 设计几乎正是我目前实现的,我可以将 IDisposable
添加到 IGameState
但我不认为它是一个好的设计选择,毕竟不是所有IGameState
有资源。此外,从任何活动 IGameState
都可以 return 新状态的意义上说,该状态机是动态的。所以 Game
真的不知道哪些是一次性的,哪些是一次性的,所以它必须对所有东西进行测试。
所以这让我问了几个问题:
- 如果我有一个 class 声明对非密封类型参数的所有权(例如 Game 的 ctor 中的
initialState
),我是否应该始终假设它可以是 IDisposable
? (可能不是)
- 如果我有一个
IDisposable
实例,我是否应该通过转换为未实现 IDisposable
的基础来放弃其所有权? (可能没有)
我从中了解到 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()
如果你有兴趣,我再谈这个。
我正在开发我的小游戏项目,作为学习和练习 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 的状态机。我们可以有 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
,因为它不知道应该处理它,并且调用代码无法轻易知道游戏何时不再需要它。这个 class 设计几乎正是我目前实现的,我可以将 IDisposable
添加到 IGameState
但我不认为它是一个好的设计选择,毕竟不是所有IGameState
有资源。此外,从任何活动 IGameState
都可以 return 新状态的意义上说,该状态机是动态的。所以 Game
真的不知道哪些是一次性的,哪些是一次性的,所以它必须对所有东西进行测试。
所以这让我问了几个问题:
- 如果我有一个 class 声明对非密封类型参数的所有权(例如 Game 的 ctor 中的
initialState
),我是否应该始终假设它可以是IDisposable
? (可能不是) - 如果我有一个
IDisposable
实例,我是否应该通过转换为未实现IDisposable
的基础来放弃其所有权? (可能没有)
我从中了解到 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()
如果你有兴趣,我再谈这个