top | item 46410850

(no title)

brewmarche | 2 months ago

In C# the closest analogue to a C++ destructor would probably be a `using` block. You’d have to remember to write `using` in front of it, but there are static analysers for this. It gets translated to a `try`–`finally` block under the hood, which calls `Dispose` in `finally`.

    using (var foo = new Foo())
    {
    }
    // foo.Dispose() gets called here, even if there is an exception
Or, to avoid nesting:

    using var foo = new Foo(); // same but scoped to closest current scope
These also is `await using` in case the cleanup is async (`await foo.DisposeAsync()`)

I think Java has something similar called try with resources.

discuss

order

cogman10|2 months ago

Java's is

    try (var foo = new Foo()) {
    }
    // foo.close() is called here.
I like the Java method for things like files because if the there's an exception during the close of a file, the regular `IOException` block handles that error the same as it handles a read or write error.

mort96|2 months ago

What do you do if you wanna return the file (or an object containing the file) in the happy path but close it in the error path?

mort96|2 months ago

That approach doesn't allow you to move the file into some long lived object or return it in the happy path though, does it?

actionfromafar|2 months ago

As someone coming from RAII to C#, you get used to it, I'd say. You "just" have to think differently. Lean into records and immutable objects whenever you can and IDisposable interface ("using") when you can't. It's not perfect but neither is RAII. I'm on a learning path but I'd say I'm more productive in C# than I ever was in C++.

brewmarche|2 months ago

You can move the burden of disposing to the caller (return the disposable object and let the caller put it in a using statement).

In addition, if the caller itself is a long-lived object it can remember the object and implement dispose itself by delegating. Then the user of the long-lived object can manage it.