Proposal: change the semantics of a using declaration combined with an object initializer #8806
Replies: 9 comments
-
I think it's worth exploring. Even if it has the potential to change the semantics of more existing code, I think that change is a positive one and will more likely correct an existing problem than cause regressions. |
Beta Was this translation helpful? Give feedback.
-
I actually agree , but wanted to stress that even if it was decided that changing the semantics of that is too daring, it should not block changing the semantics of the declaration syntax to maintain a foolish consistency. |
Beta Was this translation helpful? Give feedback.
-
Doesn't this |
Beta Was this translation helpful? Give feedback.
-
@ronnygunawan It's not necessarily appropriate for every kind of disposable object to have a finalizer. It makes sense for objects that just own unmanaged state, but people have come to use |
Beta Was this translation helpful? Give feedback.
-
@alexrp fair enough. I also want to add a simpler case: class MyDisposable : IDisposable {
readonly IDisposable x;
public MyDisposable(object someArg) {
x = new ...;
// if (someArg is null)
throw new ArgumentNullException();
}
public void Dispose() {
x?.Dispose();
}
} The bug can still be reproduced here without object initializer. |
Beta Was this translation helpful? Give feedback.
-
@ronnygunawan That's not a common scenario -- this is why people do things like argument checking before running any non-trivial code in the method, especially a constructor. A more realistic scenario would be this:
But the purpose of this proposal is not to eliminate all possible problems with partially initialized objects leaving things undisposed (that's impossible, unfortunately) but to make an intuitive pattern for initialization safe without the developer having to take special precautions, and without analyzers hassling you that you're doing something wrong, just because of the way the compiler chose to implement things. |
Beta Was this translation helpful? Give feedback.
-
@ronnygunawan, errors and exceptions during execution of the constructor are responsibility of that object, I don't think those should be caught by the |
Beta Was this translation helpful? Give feedback.
-
To provide an example that’s outside of constructor failures: void Main()
{
using var foo = new Foo
{
Prop = GetValue(),
};
}
public int GetValue() => throw new ArgumentException("derp");
public class Foo : IDisposable
{
public int Prop { get; set; }
public Foo() => Console.WriteLine("Foo");
public void Dispose() => Console.WriteLine("Foo disposed");
} Here, the |
Beta Was this translation helpful? Give feedback.
-
similar topic is being discussed here as well |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Consider the following:
The compiler will currently translate that into the moral equivalent of:
Which exposes the original code as having a bug: if the initializer throws, the
Dispose
is never called, though it really ought to be. This matches the semantics ofusing (var myDisposable = new MyDisposable { I = 0 })
, which analyzers have warned about for a while now (the infamous CA2000).The problem is that the new
using
declaration syntax is a really inviting target for refactoring what would appear to be "verbose" initialization:In fact, public versions of VS offer to refactor this to initializer syntax, which under the current semantics is a bug.
But instead of fixing this bug and sharpening the analyzers to give more meaningful messages about this, is there any objection to digging a pit of success and making the semantics of this statement match what would be the actual intent of the developer in almost all cases? That is, to have
using var x = new C { X = ... , Y = ... }
be translated to the equivalent ofvar x = new C(); using (x) { x.X = ..; x.Y = ..; }
?The cons are obvious: the semantics would not match the existing
using
block semantics closely anymore, which is potentially confusing (and harder for the compiler to get correct), and it is not backwards compatible, meaning that any code bases that actually expect theDispose
to not be called when using an object initializer that throws would break -- but I expect real-world code that relies on this to be much rarer than currently incorrect code that would want the opposite.Importantly, I do not propose to change the semantics of
using (var x = new C { X = ... })
, because this pattern, even though also potentially broken, has been in code bases for much longer, and the impact of changing the semantics there are harder to gauge.using var
has not been in the language for long enough to be entrenched like this, and is also much more visually inviting to initializer refactoring, even though under current rules it's not safe.Beta Was this translation helpful? Give feedback.
All reactions