unusing statements #7611
Replies: 5 comments 41 replies
-
I'm personally not a fan of the concept of taking what is declared as a resource to be disposed and having a language feature that toggles that off and on. It makes it more difficult to read the code and understand what it's expected to do as now you have to internalize the entire method body. I'd much prefer a resource wrapper that you could use around the resource that could be used to control disposing and could be disabled. It's not free, but one tactic I've used to achieve similar is through a ResourceBuilder-like class, where you can create and register the temporary resources intended to be used to build the final resource. If you build the final resource through the resource builder it relinquishes ownership of all of the temporary resources, but if, for whatever reason, you don't build that final resource, then all of the temporary resources would then be disposed. Something like this: using (var builder = new ResourceBuilder()) {
var bar = builder.Using(new Bar());
var baz = builder.Using(new Baz());
var qux = builder.Using(new Qux());
return builder.Build(() => new Foo(bar, baz, qux));
} |
Beta Was this translation helpful? Give feedback.
-
Can't you just continue to use the same bool TryCreateFoo(out Foo? foo)
{
foo = null;
if (!TryCreateBar(out var bar))
{
return false;
}
if (!TrySomeFunctionsThatMayThrow() || !TryCreateBaz(out var baz))
{
bar.Dispose();
return false;
}
if (!TrySomeFunctionsThatMayThrow() || !TryCreateQux(out var qux))
{
bar.Dispose();
baz.Dispose();
return false;
}
if (!TrySomeFunctionsThatMayThrow())
{
bar.Dispose();
baz.Dispose();
qux.Dispose();
return false;
}
foo = new Foo(bar, baz, qux);
return true;
}
bool TrySomeFunctionsThatMayThrow()
{
try
{
SomeFunctionsThatMayThrow();
return true;
}
catch
{
return false;
}
} |
Beta Was this translation helpful? Give feedback.
-
What about: bool TryCreateFoo(out Foo foo)
{
Bar? bar = null;
Baz? baz = null;
Quux? quux = null;
var success = false;
try
{
success =
TryCreateBar(out bar) &&
TryCreateBaz(out baz) &&
TryCreateQuux(out quux);
if (!success)
return false;
foo = new Foo(bar, baz, quux);
return true;
}
finally
{
if (!success)
{
bar?.Dispose();
baz?.Dispose();
quux?.Dispose();
}
}
} ? |
Beta Was this translation helpful? Give feedback.
-
Isn't what we really need destructible types and move semantics? |
Beta Was this translation helpful? Give feedback.
-
I am closing this as I have opened instead #7620 which I now think is a better solution to this problem. If I am supposed to wait for a team member to close this in my behalf, I can open it again. :) |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Every so often, there is a new issue opened here for RAII in C#. I am a fan of C++ and have also thought about similar things being added to C#, however it is clear from the responses to previous issues that although desired by many developers, RAII syntax in the traditional form of destructible types is not the solution to explicit resource management in C#. I will instead propose additions to the "using statement" syntax that should help solve some of the problems where RAII helps, while being able to still use
IDisposable
as its core.While writing C# that interfaces with lower-level C APIs, it's often very difficult to write exception-safe code. The "ideal" way to interface with unmanaged resources in C# would be to wrap all resource creation in managed classes capable of using primitive such as
GCHandle
to correctly manage their lifetimes and dependencies. The problem is this is very verbose when you start dealing with APIs with complex dependency graphs such as Vulkan, where to do everything correctly would require very complex reference counting behind the scenes to ensure that finalizers are run in the correct order while keeping weak references to parents that are needed for API operations and destruction. The compromise that is often made is to instead model unmanaged resources asIDisposable
instances and take care of clean up code manually. Unfortunately, although this may seem as if you were writing something similar to C at this point, and acceptable for lower-level code, the presence of exceptions in C# makes this code inherently exception unsafe, something which really shouldn't be the case.When trying to write exception safe code, RAII is often shown as a solution in many cases, but more specifically the ability to tie lifetimes of variables to a scope is the important part. Thankfully in C# we have this to a degree in the form of "using statements" and
IDisposable
. As of right now however, there are some shortcomings.Consider the following example:
Here we are using a try-pattern function to create some resource which has explicit resource management and which requires a number of dependencies which also require explicit resource management. The issue is that by default, we need to delete these manually. When working with more complex APIs, this can get quite out of hand. For example I have Vulkan related objects that can have 10s of variables that need to be cleaned up in the case of failure. It should be noted that we could argue that an exception based control flow instead of the try-pattern would be less verbose, but we get memory leaks here and the only way to do so while being exception safe is to have a separate try/catch/finally block for each resource we allocate.
If we tried to use using statements in out variables as suggested in #3798 or #2894 to solve this problem instead, we might end up with a somewhat cleaner solution that is most importantly exception safe, but still has a fatal problem:
Note I am using this "using on out var" proposed syntax from the above discussions as it makes this very concise, but the same outcome is possible in a more verbose way with the current syntax.
The issue is this of course does not work, as we are constructing
Foo
with variables that will be disposed once the function returns and the scope is exited. Aside from that, without needing to recreate an entire new model for RAII object types, we have managed to make exception safe resources at the very least at a function level while still utilizing theIDisposable
pattern. As this is an ideal syntax, I would propose an addition that makes this work in practice.The addition would be the ability to "undo" a using declaration such that the variable is no longer disposed if the scope is exited after this "undo" has occurred. See the following tentative syntax:
Here we introduce "unusing statements" as a way to tell the compiler that after this point, the variable should no longer be automatically disposed on scope exit. This small feature allows us to write full exception safe code like above while dealing with resources that depend on
IDisposable
for their lifetime management. I understand that microsofts own libraries moved away from having resources depend onIDisposable
with WPF for instance, but there are many times where this is often a lot of work for what could be easily managed at the function level like above. Theunusing
keyword here is obviously quite silly (not that I can think of anything better), but the functionality it provides would make a lot of code that I have at the moment considerably easier to feel safe writing. With a focus on AOT (the reason I am writing this Vulkan related code rather than using C++ as I normally would), I feel C# could use some better tools for writing exception safe code when dealing with manual resources rather than pushing it to the side and considering a bad experience for "low level" or "implementation detail" code to be acceptable.With this in mind, I would consider what this feature might allow if it was to be implemented in full. Specifically, that the nature of a "using declaration" an "unusing declaration" might become more general. For example, we might allow
using x;
as a standalone syntax to start "using" a variable in a scope. As such writing code such ausing x; unusing x; using x; unusing x;
would become legal and might have its uses, but is more to show that a generalised form of the syntax is possible. Naturally "using" a variable that is currently "used", or "unusing" a variable that isn't, would be a compile time error as this should be fully detectable and required for codegen to make sense.Edit: I have redacted the final section of my initial post as I now believe this work can be done by analyzers and is unrelated to the addition of "unnusing statements" and its corresponding syntax.
I appreciate the time and have other alternative syntax that might work for this, but this was the minimal product that I felt was needed to make the code enjoyable to write. Just for fun however, I will share how a much more complete solution might help for more explicit resource management:This more complete syntax with "using modifiers" for function arguments would stop issues withIDisposable
that occur even now where people pass in variables that are part of a using statement, and end up with disposed objects in their data structures. I feel if this is at all interesting it should probably be its own issue, but I have included it here for now as it could also be seen as helping to solve the same problem.Beta Was this translation helpful? Give feedback.
All reactions