Joh*_*ell 107 c# exception-handling raii
我经常在C++中使用的东西是让一个类通过构造函数和析构函数A处理另一个类的状态入口和退出条件,以确保如果该范围内的某些东西抛出异常,那么B将具有已知状态范围已退出.就首字母缩略词而言,这不是纯粹的RAII,但它仍然是一种既定的模式.BA
在C#中,我经常想做
class FrobbleManager
{
...
private void FiddleTheFrobble()
{
this.Frobble.Unlock();
Foo(); // Can throw
this.Frobble.Fiddle(); // Can throw
Bar(); // Can throw
this.Frobble.Lock();
}
}
Run Code Online (Sandbox Code Playgroud)
这需要像这样做
private void FiddleTheFrobble()
{
this.Frobble.Unlock();
try
{
Foo(); // Can throw
this.Frobble.Fiddle(); // Can throw
Bar(); // Can throw
}
finally
{
this.Frobble.Lock();
}
}
Run Code Online (Sandbox Code Playgroud)
如果我想保证退货Frobble时的状态FiddleTheFrobble.代码会更好
private void FiddleTheFrobble()
{
using (var janitor = new FrobbleJanitor(this.Frobble))
{
Foo(); // Can throw
this.Frobble.Fiddle(); // Can throw
Bar(); // Can throw
}
}
Run Code Online (Sandbox Code Playgroud)
这里FrobbleJanitor看起来大致像
class FrobbleJanitor : IDisposable
{
private Frobble frobble;
public FrobbleJanitor(Frobble frobble)
{
this.frobble = frobble;
this.frobble.Unlock();
}
public void Dispose()
{
this.frobble.Lock();
}
}
Run Code Online (Sandbox Code Playgroud)
这就是我想要的方式.现在现实赶上,因为我想使用要求的FrobbleJanitor使用与 using.我可以认为这是一个代码审查问题,但有些事情在唠叨我.
问题:以上是否会被视为滥用using和IDisposable?
Eri*_*ert 69
我认为这是滥用使用声明.我知道我在这个位置上占少数.
我认为这是一种滥用行为有三个原因.
首先,因为我希望"使用"用于使用资源并在完成后处理它.更改程序状态不使用资源,更改它不会丢弃任何内容.因此,"使用"变异和恢复状态是一种滥用; 这段代码误导了随意的读者.
其次,因为我希望"使用"是出于礼貌而非必要性.你完成它后使用"使用"处理文件的原因不是因为有必要这样做,而是因为它是礼貌的 - 其他人可能正在等待使用该文件,所以说"完成了"现在"在道德上是正确的事情.我希望我能够重构"使用",以便将使用过的资源保留更长时间,并在以后处理,并且这样做的唯一影响是稍微给其他进程带来不便."使用"块,对程序状态具有语义影响 是滥用,因为它隐藏了一个重要的,必要的程序状态突变,在一个看起来像方便和礼貌,而不是必要的结构.
第三,你的计划的行动是由其国家决定的; 谨慎操纵国家的必要性正是我们首先进行这种对话的原因.让我们考虑一下如何分析原始程序.
如果你把它带到我办公室的代码审查中,我要问的第一个问题是"如果抛出异常,锁定欺骗是否真的正确?" 从你的程序中可以明显地看出,无论发生什么事情,这件事都会积极地重新锁定.是对的吗? 抛出异常.该程序处于未知状态.我们不知道Foo,Fiddle或Bar是否扔了,他们为什么扔了,或者他们对其他没有清理的状态发生了什么突变.你能说服我,在那种可怕的情况下,重新锁定总是正确的吗?
也许是,也许不是.我的观点是,使用最初编写的代码,代码审阅者知道提出问题.使用"使用"的代码,我不知道问这个问题; 我假设"using"块分配一个资源,稍微使用它,并在完成时礼貌地处理它,而不是"using"块的右括号在异常情况下在异常情况下改变我的程序状态程序状态一致性条件已被违反.
使用"using"块具有语义效果使得该程序片段:
}
Run Code Online (Sandbox Code Playgroud)
非常有意义.当我看到那个单一的紧密支撑时,我不会立即认为"该支具具有副作用,这对我的程序的全局状态具有深远的影响".但是,当你像这样滥用"使用"时,它会突然发生.
如果我看到你的原始代码,我要问的第二件事是"如果在解锁之后但在输入尝试之前抛出异常会发生什么?" 如果您正在运行非优化程序集,则编译器可能在尝试之前插入了无操作指令,并且可能会在无操作上发生线程中止异常.这种情况很少见,但它确实发生在现实生活中,特别是在Web服务器上.在这种情况下,解锁发生但锁永远不会发生,因为在尝试之前抛出了异常.完全有可能这个代码容易受到这个问题的影响,并且应该实际编写
bool needsLock = false;
try
{
// must be carefully written so that needsLock is set
// if and only if the unlock happened:
this.Frobble.AtomicUnlock(ref needsLock);
blah blah blah
}
finally
{
if (needsLock) this.Frobble.Lock();
}
Run Code Online (Sandbox Code Playgroud)
也许它确实如此,也许它没有,但我知道问这个问题.对于"使用"版本,它容易出现同样的问题:在Frobble被锁定之后但在输入与使用相关联的尝试保护区域之前,可能抛出线程中止异常.但是使用"使用"版本,我认为这是"那么什么?" 情况.不幸的是,如果发生这种情况,但我认为"使用"只是礼貌,而不是改变极其重要的程序状态.我假设如果在错误的时间发生了一些可怕的线程中止异常,那么,好吧,垃圾收集器最终将通过运行终结器来清理该资源.
And*_*tan 32
我不这么认为,必然.IDisposable的技术上是指用于事物具有非托管资源,但随后的使用指令是实现的一个常见的模式只是一种巧妙的方法try .. finally { dispose }.
一个纯粹主义者会争辩说"是的 - 它是滥用的",而在纯粹主义意义上它是; 但我们大多数人不是从纯粹的角度编码,而是从半艺术角度编码.在我看来,以这种方式使用"使用"结构确实非常具有艺术性.
您可能应该在IDisposable上添加另一个接口以将其推得更远,向其他开发人员解释为什么该接口意味着IDisposable.
这样做有很多其他选择,但最终,我想不出任何会像这样整洁,所以去吧!
her*_*ter 27
如果你只想要一些干净的范围代码,你也可以使用lambdas,ála
myFribble.SafeExecute(() =>
{
myFribble.DangerDanger();
myFribble.LiveOnTheEdge();
});
Run Code Online (Sandbox Code Playgroud)
其中.SafeExecute(Action fribbleAction)方法包装try- catch- finally块.
Sam*_*ack 24
加入C#语言设计团队的Eric Gunnerson给出了几乎相同问题的答案:
道格问道:
re:带超时的锁定语句...
在使用多种方法处理常见模式之前,我已经完成了这个技巧.通常锁定获取,但也有一些其他.问题是它总是感觉像是一个黑客,因为对象实际上不是一次性的,而是" 回到最后一个范围内 ".
道格,
当我们决定[sic] using语句时,我们决定将其命名为"using",而不是更具体地处理对象,以便它可以用于这种情况.
Han*_*ant 11
这是一个滑坡.IDisposable有一份合同,一份由终结者支持的合同.在你的情况下,终结器是没用的.您不能强制客户端使用using语句,只能鼓励他这样做.您可以使用以下方法强制它:
void UseMeUnlocked(Action callback) {
Unlock();
try {
callback();
}
finally {
Lock();
}
}
Run Code Online (Sandbox Code Playgroud)
但是如果没有lamdas,这往往会有点尴尬.也就是说,我像你一样使用了IDisposable.
然而,你的帖子中有一个细节,使得它非常接近反模式.您提到这些方法可以抛出异常.这不是呼叫者可以忽略的.他可以做三件事:
后两者要求调用者显式写一个try块.现在,using语句会受到妨碍.它可能会诱使客户陷入昏迷状态,使他相信你的班级正在照顾国家,而且不需要做额外的工作.这几乎从来都不准确.
一个真实世界的例子是ASP.net MVC的BeginForm.基本上你可以写:
Html.BeginForm(...);
Html.TextBox(...);
Html.EndForm();
Run Code Online (Sandbox Code Playgroud)
要么
using(Html.BeginForm(...)){
Html.TextBox(...);
}
Run Code Online (Sandbox Code Playgroud)
Html.EndForm调用Dispose,Dispose只输出</form>标记.关于这一点的好处是{}括号创建了一个可见的"范围",这使得更容易看到表单中的内容和不是什么.
我不会过度使用它,但基本上IDisposable只是一种说"当你完成它时你必须调用这个功能"的方法.MvcForm使用它来确保表单关闭,Stream使用它来确保流关闭,您可以使用它来确保对象被解锁.
就个人而言,如果以下两条规则属实,我只会使用它,但我是由我设定的:
最后,这完全取决于期望.如果一个对象实现了IDisposable,我认为它需要做一些清理,所以我称之为.我认为它通常胜过"关机"功能.
话虽这么说,我不喜欢这句话:
this.Frobble.Fiddle();
Run Code Online (Sandbox Code Playgroud)
由于FrobbleJanitor现在"拥有"Frobble,我想知道在看门人的Frobble上打电话给Fiddle是不是更好?
| 归档时间: |
|
| 查看次数: |
6151 次 |
| 最近记录: |