Why is the reset ID C # 7 _ still working in the use block?

So, the template that I use very often when working on my UWP application is to use an instance of SemaphoreSlim to avoid race conditions (I prefer not to use lock , because this requires an additional target, t block asynchronously).

A typical snippet would look like this:

 private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1); public async Task FooAsync() { await Semaphore.WaitAsync(); // Do stuff here Semaphore.Release(); } 

With an extra try/finally block around everything, if the code between them could fail, but I want the semaphore to work correctly.

To reduce the pattern, I tried to write a wrapper class that would have the same behavior (including the try/finally bit) with less code needed. I also did not want to use delegate , as this would create an object every time, and I just wanted to reduce my code without changing the way it worked.

I came up with this class (comments removed for brevity):

 public sealed class AsyncMutex { private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1); public async Task<IDisposable> Lock() { await Semaphore.WaitAsync().ConfigureAwait(false); return new _Lock(Semaphore); } private sealed class _Lock : IDisposable { private readonly SemaphoreSlim Semaphore; public _Lock(SemaphoreSlim semaphore) => Semaphore = semaphore; void IDisposable.Dispose() => Semaphore.Release(); } } 

And how it works is that using it you only need the following:

 private readonly AsyncMutex Mutex = new AsyncMutex(); public async Task FooAsync() { using (_ = await Mutex.Lock()) { // Do stuff here } } 

One line is shorter and with try/finally built-in ( using block) is awesome.

Now I have no idea why this works, even though the reset operator b.

This drop of _ is really just out of curiosity, because I knew that I just had to write var _ , since I needed an IDisposable object that would be used at the end of the using block, not discarder.

But, to my surprise, the same IL is generated for both methods:

 .method public hidebysig instance void T1() cil managed { .maxstack 1 .locals init ( [0] class System.Threading.Tasks.AsyncMutex mutex, [1] class System.IDisposable V_1 ) IL_0001: newobj instance void System.Threading.Tasks.AsyncMutex::.ctor() IL_0006: stloc.0 // mutex IL_0007: ldloc.0 // mutex IL_0008: callvirt instance class System.Threading.Tasks.Task`1<class System.IDisposable> System.Threading.Tasks.AsyncMutex::Lock() IL_000d: callvirt instance !0/*class System.IDisposable*/ class System.Threading.Tasks.Task`1<class System.IDisposable>::get_Result() IL_0012: stloc.1 // V_1 .try { // Do stuff here.. IL_0025: leave.s IL_0032 } finally { IL_0027: ldloc.1 // V_1 IL_0028: brfalse.s IL_0031 IL_002a: ldloc.1 // V_1 IL_002b: callvirt instance void System.IDisposable::Dispose() IL_0031: endfinally } IL_0032: ret } 

The "discarder" IDisposable is stored in field V_1 and is placed correctly.

So why is this happening? docs says nothing about the reset statement that is used with the using block, and they just say that the reset destination is completely ignored.

Thanks!

+7
c # visual-studio cil
source share
2 answers

The using statement does not require an explicit declaration of a local variable. An expression is also allowed.

The language specification defines the following syntax.

 using_statement : 'using' '(' resource_acquisition ')' embedded_statement ; resource_acquisition : local_variable_declaration | expression ; 

If the form resource_acquisition is local_variable_declaration, then the file type local_variable_declaration must be either dynamic or a type that can be implicitly converted to System.IDisposable . If the resource_acquisition form is an expression, then this expression must be implicitly convertible to System.IDisposable .

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/statements#the-using-statement

Assigning an existing variable (or discarding the result) is also an expression. For example, the following code compiles:

 var a = (_ = 10); 
+7
source share

Using the discard function is really a red herring here. The reason this works is because the using statement can accept an expression that resolves the value to be deleted (in addition to the alternative syntax declaring the variable). In addition, the assignment operator resolves the assigned value.

The value that you provide on the right side of the assignment statement is your Lock object, so this _ = await Mutex.Lock() expression solves it. Since this value (not as a variable declaration, but as a separate value) is one-time, this is what will be cleared at the end of using .

+4
source share

All Articles