强制“结束”每当有相应的“开始”时就调用称呼

发布于 2024-08-30 04:13:27 字数 650 浏览 7 评论 0原文

假设我想执行一条规则:

每次在函数中调用“StartJumping()”时,都必须在返回之前调用“EndJumping()”。

当开发人员编写代码时,他们可能只是忘记调用 EndSomething - 所以我想让它更容易记住。

我只能想到一种方法来做到这一点:它滥用了“using”关键字:

class Jumper : IDisposable {
    public Jumper() {   Jumper.StartJumping(); }
    public void Dispose() {  Jumper.EndJumping(); }

    public static void StartJumping() {...}
    public static void EndJumping() {...}
}

public bool SomeFunction() {
    // do some stuff

    // start jumping...
    using(new Jumper()) {
        // do more stuff
        // while jumping

    }  // end jumping
}

有更好的方法来做到这一点吗?

Let's say I want to enforce a rule:

Everytime you call "StartJumping()" in your function, you must call "EndJumping()" before you return.

When a developer is writing their code, they may simply forget to call EndSomething - so I want to make it easy to remember.

I can think of only one way to do this: and it abuses the "using" keyword:

class Jumper : IDisposable {
    public Jumper() {   Jumper.StartJumping(); }
    public void Dispose() {  Jumper.EndJumping(); }

    public static void StartJumping() {...}
    public static void EndJumping() {...}
}

public bool SomeFunction() {
    // do some stuff

    // start jumping...
    using(new Jumper()) {
        // do more stuff
        // while jumping

    }  // end jumping
}

Is there a better way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

街角卖回忆 2024-09-06 04:13:27

本质上,问题是:

  • 我有全局状态......
  • 并且我想改变该全局状态......
  • 但我想确保我将其改变回来。

您发现这样做时会很痛。我的建议是,与其试图找到一种减少痛苦的方法,不如尝试找到一种从一开始就不做痛苦的事情的方法。

我很清楚这有多难。当我们在 v3 中向 C# 添加 lambda 时,我们遇到了一个大问题。考虑以下问题:

void M(Func<int, int> f) { }
void M(Func<string, int> f) { }
...
M(x=>x.Length);

我们到底如何成功绑定它?好吧,我们要做的就是尝试两者(x 是 int,或 x 是 string),看看哪一个(如果有的话)会给我们带来错误。那些没有给出错误的将成为重载解决方案的候选者。

编译器中的错误报告引擎是全局状态。在 C# 1 和 2 中,从来没有出现过这样的情况:我们不得不说“绑定整个方法体以确定它是否有任何错误但不报告错误”。毕竟,在这个程序中,您不想想要收到错误“int 没有名为 Length 的属性”,您希望它发现这一点,记下它,而不是报告它。

所以我所做的正是你所做的。开始抑制错误报告,但不要忘记停止抑制错误报告。

太可怕了。我们真正应该做的是重新设计编译器,以便错误是语义分析器的输出,而不是编译器的全局状态。然而,很难将其贯穿数十万行依赖于全局状态的现有代码。

无论如何,还有其他事情要考虑。您的“使用”解决方案具有在引发异常时停止跳转的效果。 这是正确的做法吗?可能不是。毕竟,抛出了意外的、未处理的异常。整个系统可能非常不稳定。在这种情况下,您的内部状态不变量可能实际上都不是不变的。

这样看:我改变了全局状态。然后我遇到了一个意外的、未处理的异常。我知道,我想我会再次改变全局状态!那会有帮助的!看起来是一个非常非常糟糕的主意。

当然,这取决于全局状态的突变是什么。如果它是“再次开始向用户报告错误”,就像在编译器中一样,那么对于未处理的异常,正确的做法是再次开始向用户报告错误:毕竟,我们'我们需要报告编译器刚刚出现未处理的异常的错误!

另一方面,如果全局状态的突变是“解锁资源并允许不可信代码观察和使用它”,那么自动解锁它可能是一个非常糟糕的想法。这种意外的、未处理的异常可能是对您的代码进行攻击的证据,攻击者非常希望您能够解锁对全局状态的访问,因为它处于易受攻击的、不一致的形式。

Essentially the problem is:

  • I have global state...
  • and I want to mutate that global state...
  • but I want to make sure that I mutate it back.

You have discovered that it hurts when you do that. My advice is rather than trying to find a way to make it hurt less, try to find a way to not do the painful thing in the first place.

I am well aware of how hard this is. When we added lambdas to C# in v3 we had a big problem. Consider the following:

void M(Func<int, int> f) { }
void M(Func<string, int> f) { }
...
M(x=>x.Length);

How on earth do we bind this successfully? Well, what we do is try both (x is int, or x is string) and see which, if any, gives us an error. The ones that don't give errors become candidates for overload resolution.

The error reporting engine in the compiler is global state. In C# 1 and 2 there had never been a situation where we had to say "bind this entire method body for the purposes of determining if it had any errors but don't report the errors". After all, in this program you do not want to get the error "int doesn't have a property called Length", you want it to discover that, make a note of it, and not report it.

So what I did was exactly what you did. Start suppressing error reporting, but don't forget to STOP suppressing error reporting.

It's terrible. What we really ought to do is redesign the compiler so that errors are output of the semantic analyzer, not global state of the compiler. However, it's hard to thread that through hundreds of thousands of lines of existing code that depends on that global state.

Anyway, something else to think about. Your "using" solution has the effect of stopping jumping when an exception is thrown. Is that the right thing to do? It might not be. After all, an unexpected, unhandled exception has been thrown. The entire system could be massively unstable. None of your internal state invariants might be actually invariant in this scenario.

Look at it this way: I mutated global state. I then got an unexpected, unhandled exception. I know, I think I'll mutate global state again! That'll help! Seems like a very, very bad idea.

Of course, it depends on what the mutation to global state is. If it is "start reporting errors to the user again" as it is in the compiler then the correct thing to do for an unhandled exception is to start reporting errors to the user again: after all, we're going to need to report the error that the compiler just had an unhandled exception!

If on the other hand the mutation to global state is "unlock the resource and allow it to be observed and used by untrustworthy code" then it is potentially a VERY BAD IDEA to automatically unlock it. That unexpected, unhandled exception might be evidence of an attack on your code, from an attacker who is dearly hoping that you are going to unlock access to global state now that it is in a vulnerable, inconsistent form.

清醇 2024-09-06 04:13:27

我不同意埃里克的观点:何时这样做或不这样做取决于具体情况。有一次,我正在重新设计我的大型代码库,以包含围绕对自定义图像类的所有访问的获取/释放语义。图像最初分配在不可移动的内存块中,但我们现在能够将图像放入允许移动的块中(如果尚未获取它们)。在我的代码中,内存块滑过解锁状态是一个严重的错误。

因此,强制执行这一点至关重要。我创建了这个类:

public class ResourceReleaser<T> : IDisposable
{
    private Action<T> _action;
    private bool _disposed;
    private T _val;

    public ResourceReleaser(T val, Action<T> action)
    {
        if (action == null)
            throw new ArgumentNullException("action");
        _action = action;
        _val = val;
    }

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

    ~ResourceReleaser()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
            _disposed = true;
            _action(_val);
        }
    }
}

它允许我创建这个子类:

public class PixelMemoryLocker : ResourceReleaser<PixelMemory>
{
    public PixelMemoryLocker(PixelMemory mem)
        : base(mem,
        (pm =>
            {
                if (pm != null)
                    pm.Unlock();
            }
        ))
    {
        if (mem != null)
            mem.Lock();
    }

    public PixelMemoryLocker(AtalaImage image)
        : this(image == null ? null : image.PixelMemory)
    {
    }
}

这又让我编写这个代码:

using (var locker = new PixelMemoryLocker(image)) {
    // .. pixel memory is now locked and ready to work with
}

这完成了我需要的工作,快速搜索告诉我我在 186 个地方需要它,我可以保证永远不会失败解锁。我必须能够做出这样的保证 - 否则可能会冻结我的客户端堆中的一大块内存。我不能那样做。

然而,在我处理 PDF 文档加密的另一种情况下,所有字符串和流都在 PDF 字典中加密,除非它们没有加密。真的。在极少数边缘情况下,加密或解密字典是不正确的,因此在流式传输对象时,我会这样做:

if (context.IsEncrypting)
{
    crypt = context.Encryption;
    if (!ShouldBeEncrypted(crypt))
    {
        context.SuspendEncryption();
        suspendedEncryption = true;
    }
}
// ... more code ...
if (suspendedEncryption)
{
    context.ResumeEncryption();
}

那么为什么我选择这种方法而不是 RAII 方法呢?好吧,在...more代码中发生的任何异常...都意味着你已经死在水里了。没有恢复。不可能恢复。您必须从头开始,并且需要重建上下文对象,因此无论如何它的状态都是固定的。相比之下,我只需要执行此代码 4 次 - 发生错误的可能性比内存锁定代码要少得多,并且如果我将来忘记了一个,生成的文档将立即被破坏(失败快速地)。

因此,当您绝对必须进行括号内的调用并且不能失败时,请选择 RAII。
如果其他做法很简单,请不要费心使用 RAII。

I'm going to disagree with Eric: when to do this or not depends on the circumstances. At one point, I was reworking my a large code base to include acquire/release semantics around all accesses to a custom image class. Images were originally allocated in unmoving blocks of memory, but we now had the ability to put the images into blocks that were allowed to be moved if they hadn't been acquired. In my code, it is a serious bug for a block of memory to have slipped past unlocked.

Therefore, it is vital to enforce this. I created this class:

public class ResourceReleaser<T> : IDisposable
{
    private Action<T> _action;
    private bool _disposed;
    private T _val;

    public ResourceReleaser(T val, Action<T> action)
    {
        if (action == null)
            throw new ArgumentNullException("action");
        _action = action;
        _val = val;
    }

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

    ~ResourceReleaser()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
            _disposed = true;
            _action(_val);
        }
    }
}

which allows me to do make this subclass:

public class PixelMemoryLocker : ResourceReleaser<PixelMemory>
{
    public PixelMemoryLocker(PixelMemory mem)
        : base(mem,
        (pm =>
            {
                if (pm != null)
                    pm.Unlock();
            }
        ))
    {
        if (mem != null)
            mem.Lock();
    }

    public PixelMemoryLocker(AtalaImage image)
        : this(image == null ? null : image.PixelMemory)
    {
    }
}

Which in turn lets me write this code:

using (var locker = new PixelMemoryLocker(image)) {
    // .. pixel memory is now locked and ready to work with
}

This does the work I need and a quick search tells me I needed it in 186 places that I can guarantee won't ever fail to unlock. And I have to be able to make this guarantee - to do otherwise could freeze a huge chunk of memory in my client's heap. I can't do that.

However, in another case where I do work handling encryption of PDF documents, all strings and streams are encrypted in PDF dictionaries except when they're not. Really. There are a tiny number of edge cases wherein it is incorrect to encrypt or decrypt the dictionaries so while streaming out an object, I do this:

if (context.IsEncrypting)
{
    crypt = context.Encryption;
    if (!ShouldBeEncrypted(crypt))
    {
        context.SuspendEncryption();
        suspendedEncryption = true;
    }
}
// ... more code ...
if (suspendedEncryption)
{
    context.ResumeEncryption();
}

so why did I choose this over the RAII approach? Well, any exception that happens in the ... more code ... means that you are dead in the water. There is no recovery. There can be no recovery. You have to start over from the very beginning and the context object needs to be reconstructed, so it's state is hosed anyway. And by comparison, I only had to do this code 4 times - the possibility for error is way, way less than in the memory locking code, and if I forget one in the future, the generated document is going to be broken immediately (fail fast).

So pick RAII when you absolutely positively HAVE to have the bracketed call and can't fail.
Don't bother with RAII if it is trivial to do otherwise.

萌梦深 2024-09-06 04:13:27

如果您需要控制作用域操作,我将添加一个方法,该方法采用 Action 来包含跳线实例上所需的操作:

public static void Jump(Action<Jumper> jumpAction)
{
    StartJumping();
    Jumper j = new Jumper();
    jumpAction(j);
    EndJumping();
}

If you need to control a scoped operation, I would add a method which take an Action<Jumper> to contain the required operations on the jumper instance:

public static void Jump(Action<Jumper> jumpAction)
{
    StartJumping();
    Jumper j = new Jumper();
    jumpAction(j);
    EndJumping();
}
四叶草在未来唯美盛开 2024-09-06 04:13:27

在某些情况下(即当所有操作都可以在最后发生时)有效的另一种方法是创建一系列具有流畅接口和一些最终 Execute() 方法的类。

var sequence = StartJumping().Then(some_other_method).Then(some_third_method);
// forgot to do EndJumping()
sequence.Execute();

Execute() 可以向后链接并强制执行任何规则(或者您可以在构建开始序列时构建结束序列)。

与其他技术相比,该技术的一个优点是您不受范围规则的限制。例如,如果您想基于用户输入或其他异步事件构建序列,您可以这样做。

An alternative approach that would work in some circumstances (i.e. when the actions can all happen at the end) would be to create a series of classes with a fluent interface and some final Execute() method.

var sequence = StartJumping().Then(some_other_method).Then(some_third_method);
// forgot to do EndJumping()
sequence.Execute();

Execute() can chain back down line and enforce any rules (or you can build the closing sequence as you build the opening sequence).

The one advantage this technique has over others is that you aren't limited by scoping rules. e.g. if you want to build the sequence based on user inputs or other asynchronous events you can do that.

感受沵的脚步 2024-09-06 04:13:27

Jeff,

您想要实现的目标通常被称为面向方面编程( AOP)。在 C# 中使用 AOP 范例进行编程并不容易,也不可靠……目前为止。 CLR 和 .NET 框架中直接内置了一些功能,使 AOP 在某些狭窄的情况下成为可能。例如,当您从 ContextBoundObject 派生类时,您可以使用 ContextAttribute 在 CBO 实例上的方法调用之前/之后注入逻辑。您可以在此处查看如何完成此操作的示例

派生 CBO 类既烦人又具有限制性——而且还有另一种选择。您可以使用 PostSharp 等工具将 AOP 应用于任何 C# 类。 PostSharp 比 CBO 灵活得多,因为它本质上在编译后步骤中重写您的 IL 代码。虽然这看起来有点可怕,但它非常强大,因为它允许您以几乎任何您可以想象的方式编织代码。以下是基于您的使用场景构建的 PostSharp 示例:

using PostSharp.Aspects;

[Serializable]
public sealed class JumperAttribute : OnMethodBoundaryAspect
{
  public override void OnEntry(MethodExecutionArgs args) 
  { 
    Jumper.StartJumping();
  }     

  public override void OnExit(MethodExecutionArgs args) 
  { 
    Jumper.EndJumping(); 
  }
}

class SomeClass
{
  [Jumper()]
  public bool SomeFunction()  // StartJumping *magically* called...          
  {
    // do some code...         

  } // EndJumping *magically* called...
}

PostSharp 通过重写已编译的 IL 代码以包含运行您在 JumperAttribute 类中定义的代码的指令来实现神奇 ' OnEntryOnExit 方法。

在您的情况下,PostSharp/AOP 是否是比“重新调整用途”using 语句更好的替代方案,我不清楚。我倾向于同意 @Eric Lippert 的观点,即 using 关键字混淆了代码的重要语义并强加了using 块末尾的 } 符号的副作用和语义 - 这是意外的。但这与将 AOP 属性应用于代码有什么不同吗?它们还将重要的语义隐藏在声明性语法后面……但这就是 AOP 的要点。

我完全同意 Eric 的观点是,重新设计代码以避免这样的全局状态(如果可能)可能是最好的选择。它不仅避免了强制正确使用的问题,而且还可以帮助避免未来的多线程挑战 - 全局状态非常容易受到这种挑战。

Jeff,

what you're trying to achieve is generally referred to as Aspect Oriented Programming (AOP). Programming using AOP paradigms in C# is not easy - or reliable... yet. There are some capabilities built directly into the CLR and .NET framework that make AOP possible is certain narrow cases. For example, when you derive a class from ContextBoundObject you can use ContextAttribute to inject logic before/after method calls on the CBO instance. You can see examples of how this is done here.

Deriving a CBO class is annoying and restrictive - and there is another alternative. You can use a tool like PostSharp to apply AOP to any C# class. PostSharp is far more flexible than CBOs because it essentially rewrites your IL code in a postcompilation step. While this may seem a bit scary, it's very powerful because it allows you to weave in code in almost any way you can imagine. Here's a PostSharp example built on your use scenario:

using PostSharp.Aspects;

[Serializable]
public sealed class JumperAttribute : OnMethodBoundaryAspect
{
  public override void OnEntry(MethodExecutionArgs args) 
  { 
    Jumper.StartJumping();
  }     

  public override void OnExit(MethodExecutionArgs args) 
  { 
    Jumper.EndJumping(); 
  }
}

class SomeClass
{
  [Jumper()]
  public bool SomeFunction()  // StartJumping *magically* called...          
  {
    // do some code...         

  } // EndJumping *magically* called...
}

PostSharp achieves the magic by rewriting the compiled IL code to include instructions to run the code that you've defined in the JumperAttribute class' OnEntry and OnExit methods.

Whether in your case PostSharp/AOP is a better alternative than "repurposing" the using statement is unclear to me. I tend to agree with @Eric Lippert that the using keyword obfuscates important semantics of your code and imposes side-effects and semantic menting to the } symbol at the end of a using block - which is unexpected. But is this any different than applying AOP attributes to your code? They also hide important semantics behind a declarative syntax ... but that's sort of the point of AOP.

One point where I whole-heartedly agree with Eric is that redesigning your code to avoid global state like this (when possible) is probably the best option. Not only does it avoid the problem of enforcing correct usage, but it can also help avoid multithreading challenges in the future - which global state is very susceptible to.

樱桃奶球 2024-09-06 04:13:27

我实际上并不认为这是对 using 的滥用;我在不同的上下文中使用这个习语并且从未遇到过问题......特别是考虑到 using 只是一个语法糖。我使用它在我使用的第三方库之一中设置全局标志的一种方法是,以便在完成操作时恢复更改:

class WithLocale : IDisposable {
    Locale old;
    public WithLocale(Locale x) { old = ThirdParty.Locale; ThirdParty.Locale = x }
    public void Dispose() { ThirdParty.Locale = old }
}

请注意,您不需要在 using 子句中分配变量。这就足够了:

using(new WithLocale("jp")) {
    ...
}

我有点怀念 C++ 的 RAII 习惯用法,其中总是调用析构函数。我猜,using 是 C# 中最接近的。

I don't actually see this as an abuse of using; I'm using this idiom in different contexts and never had problems... especially given that using is only a syntactic sugar. One way I use it to set a global flag in one of third party libraries I use, so that the change is reverted when finishing operations:

class WithLocale : IDisposable {
    Locale old;
    public WithLocale(Locale x) { old = ThirdParty.Locale; ThirdParty.Locale = x }
    public void Dispose() { ThirdParty.Locale = old }
}

Note you don't need to assign a variable in using clause. This is enough:

using(new WithLocale("jp")) {
    ...
}

I slightly miss C++'s RAII idiom here, where the destructor is always called. using is the closest you can get in C#, I guess.

自控 2024-09-06 04:13:27

我们几乎完全按照您的建议在我们的应用程序中添加方法跟踪日志记录。无需进行 2 次日志记录调用,一次用于进入,一次用于退出。

We have done almost exactly what you propose as a way to add method trace logging in our applications. Beats having to make 2 logging calls, one for entering and one for exiting.

嗫嚅 2024-09-06 04:13:27

拥有一个抽象基类会有帮助吗?基类中的方法可以调用 StartJumping()(子类将实现的抽象方法的实现),然后调用 EndJumping()。

Would having an abstract base class be helpful? A method in the base class could call StartJumping(), the implementation of the abstract method that child classes would implement, and then call EndJumping().

征棹 2024-09-06 04:13:27

我喜欢这种风格,并且当我想保证一些拆卸行为时经常实现它:通常它比 try-finally 读起来更干净。我认为您不应该费心声明和命名引用 j,但我确实认为您应该避免调用 EndJumping 方法两次,您应该检查它是否已被释放。并参考您的非托管代码注释:它是通常为此实现的终结器(尽管通常调用 Dispose 和 SuppressFinalize 来更快地释放资源。)

I like this style and frequently implement it when I want to guarantee some tear down behaviour: often it is much cleaner to read than a try-finally. I don't think you should bother with declaring and naming the reference j, but I do think you should avoid calling the EndJumping method twice, you should check if it's already been disposed. And with reference to your unmanaged code note: it's a finalizer that's typically implemented for that (although Dispose and SuppressFinalize are typically called to free up the resources sooner.)

執念 2024-09-06 04:13:27

我已经对这里的一些答案发表了一些关于 IDisposable 是什么和不是什么的评论,但我会重申一点,IDisposable 是为了启用确定性清理,但是不保证确定性清理。即它不保证被调用,并且仅在与 using 块配对时才得到一定程度的保证。

// despite being IDisposable, Dispose() isn't guaranteed.
Jumper j = new Jumper();

现在,我不会评论您对 using 的使用,因为 Eric Lippert 在这方面做得更好。

如果您确实有一个不需要终结器的 IDisposable 类,我见过的用于检测人们何时忘记调用 Dispose() 的模式是添加一个在 DEBUG 版本中有条件编译的终结器,以便您可以每当调用终结器时都会记录一些内容。

一个现实的例子是一个类,它以某种特殊的方式封装了对文件的写入。因为 MyWriter 持有对 FileStream 的引用,而该文件流也是 IDisposable,所以出于礼貌,我们还应该实现 IDisposable

public sealed class MyWriter : IDisposable
{
    private System.IO.FileStream _fileStream;
    private bool _isDisposed;

    public MyWriter(string path)
    {
        _fileStream = System.IO.File.Create(path);
    }

#if DEBUG
    ~MyWriter() // Finalizer for DEBUG builds only
    {
        Dispose(false);
    }
#endif

    public void Close()
    {
        ((IDisposable)this).Dispose();
    }

    private void Dispose(bool disposing)
    {
        if (disposing && !_isDisposed)
        {
            // called from IDisposable.Dispose()
            if (_fileStream != null)
                _fileStream.Dispose();

            _isDisposed = true;
        }
        else
        {
            // called from finalizer in a DEBUG build.
            // Log so a developer can fix.
            Console.WriteLine("MyWriter failed to be disposed");
        }
    }

    void IDisposable.Dispose()
    {
        Dispose(true);
#if DEBUG
        GC.SuppressFinalize(this);
#endif
    }

}

哎哟。这相当复杂,但这正是人们看到 IDisposable 时所期望的。

该类除了打开一个文件之外甚至没有做任何事情,但这就是您使用 IDisposable 获得的效果,并且日志记录极其简化。

    public void WriteFoo(string comment)
    {
        if (_isDisposed)
            throw new ObjectDisposedException("MyWriter");

        // logic omitted
    }

终结器非常昂贵,并且上面的 MyWriter 不需要终结器,因此在 DEBUG 构建之外添加终结器是没有意义的。

I've commented on some of the answers here a bit about what IDisposable is and isn't, but I will reiterate the point that IDisposable is to enable deterministic cleanup, but does not guarantee deterministic cleanup. i.e. It's not guaranteed to be called, and only somewhat guaranteed when paired with a using block.

// despite being IDisposable, Dispose() isn't guaranteed.
Jumper j = new Jumper();

Now, I'm not going to comment on your use of using because Eric Lippert did a far better job of it.

If you do have an IDisposable class without requiring a finalizer, a pattern I've seen for detecting when people forget to call Dispose() is to add a finalizer that's conditionally compiled in DEBUG builds so that you can log something whenever your finalizer is called.

A realistic example is a class that's encapsulates writing to a file in some special way. Because MyWriter holds a reference to a FileStream which is also IDisposable, we should also implement IDisposable to be polite.

public sealed class MyWriter : IDisposable
{
    private System.IO.FileStream _fileStream;
    private bool _isDisposed;

    public MyWriter(string path)
    {
        _fileStream = System.IO.File.Create(path);
    }

#if DEBUG
    ~MyWriter() // Finalizer for DEBUG builds only
    {
        Dispose(false);
    }
#endif

    public void Close()
    {
        ((IDisposable)this).Dispose();
    }

    private void Dispose(bool disposing)
    {
        if (disposing && !_isDisposed)
        {
            // called from IDisposable.Dispose()
            if (_fileStream != null)
                _fileStream.Dispose();

            _isDisposed = true;
        }
        else
        {
            // called from finalizer in a DEBUG build.
            // Log so a developer can fix.
            Console.WriteLine("MyWriter failed to be disposed");
        }
    }

    void IDisposable.Dispose()
    {
        Dispose(true);
#if DEBUG
        GC.SuppressFinalize(this);
#endif
    }

}

Ouch. That's quite complicated, but this is what people expect when they see IDisposable.

The class doesn't even do anything yet but open a file, but that's what you get with IDisposable, and the logging is extremely simplified.

    public void WriteFoo(string comment)
    {
        if (_isDisposed)
            throw new ObjectDisposedException("MyWriter");

        // logic omitted
    }

Finalizers are expensive, and the MyWriter above doesn't require a finalizer, so there's no point adding one outside of DEBUG builds.

孤云独去闲 2024-09-06 04:13:27

使用 using 模式,我可以使用 grep (? 来查找可能存在问题的所有位置。

使用 StartJumping,我需要手动查看每个调用,以查明是否存在异常、返回、中断、继续、转到等可能导致 EndJumping 不被调用的可能性。

With the using pattern I can just use a grep (?<!using.*)new\s+Jumper to find all places where there might be a problem.

With StartJumping I need to manually look at each call to find out if there is a possibility that an exception, return, break, continue, goto etc can cause EndJumping to not be called.

久随 2024-09-06 04:13:27
  1. 我不认为你想让这些方法成为静态
  2. 你需要检查处理是否已经调用了结束跳转。
  3. 如果我再次调用开始跳跃会发生什么?

您可以使用参考计数器或标志来跟踪“跳跃”的状态。有些人会说 IDisposable 仅适用于非托管资源,但我认为这是可以的。否则,您应该将开始和结束跳转设为私有,并使用析构函数来配合构造函数。

class Jumper
{
    public Jumper() {   Jumper.StartJumping(); }
    public ~Jumper() {  Jumper.EndJumping(); }

    private void StartJumping() {...}
    public void EndJumping() {...}
}
  1. I dont think you want to make those methods static
  2. You need to check in the dispose if end jumping has alredy been called.
  3. If I call start jumping again what happens?

You could use a reference counter or a flag to keep track of the state of the 'jumping'. Some people would say IDisposable is only for unmanaged resources but I think this is ok. Otherwise you should make start and end jumping private and use a destructor to go with the constructor.

class Jumper
{
    public Jumper() {   Jumper.StartJumping(); }
    public ~Jumper() {  Jumper.EndJumping(); }

    private void StartJumping() {...}
    public void EndJumping() {...}
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文