为什么 C# 7 丢弃标识符 _ 仍然在 using 块中工作?
Why does the C# 7 discard identifier _ still work in a using block?
因此,我在开发 UWP 应用程序时经常使用的一种模式是使用 SemaphoreSlim
实例来避免竞争条件(我不喜欢使用 lock
,因为它需要一个额外的目标对象,并且它不会异步锁定)。
典型的代码片段如下所示:
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
public async Task FooAsync()
{
await Semaphore.WaitAsync();
// Do stuff here
Semaphore.Release();
}
随着额外的 try/finally
块围绕整个事情,如果中间的代码可能崩溃但我想保持信号量正常工作。
为了减少样板文件,我尝试编写一个包装器 class,它具有相同的行为(包括 try/finally
位),但需要的代码更少。我也不想使用 delegate
,因为那样每次都会创建一个对象,我只是想减少我的代码而不改变它的工作方式。
我想出了这个 class(为简洁起见删除了评论):
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();
}
}
它的工作方式是,通过使用它,您只需要以下内容:
private readonly AsyncMutex Mutex = new AsyncMutex();
public async Task FooAsync()
{
using (_ = await Mutex.Lock())
{
// Do stuff here
}
}
缩短了一行,并且内置了 try/finally
(using
块),太棒了。
Now, I have no idea why this works, despite the discard operator being
used.
丢弃 _ 实际上只是出于好奇,因为我知道我应该写 var _
,因为我需要在 [=20] 的末尾使用 IDisposable
对象=] 块,而不是丢弃器。
但是,令我惊讶的是,两种方法都生成了相同的 IL:
.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
}
"discarder" IDisposable
存储在字段 V_1
中并正确处理。
那么,为什么会这样呢? docs 没有说明丢弃运算符与 using
块一起使用,他们只是说丢弃赋值被完全忽略。
谢谢!
丢弃功能的使用在这里真的是一个转移注意力的问题。之所以可行,是因为 using
语句可以接受解析为要处理的值的表达式(除了声明变量的替代语法之外)。此外,赋值运算符 解析为赋值 。
您在赋值运算符右侧提供的值是您的 Lock 对象,因此这就是表达式 _ = await Mutex.Lock()
解析的内容。由于 value(不是作为变量声明,而是作为独立值)是一次性的,因此它将在 using
结束时清理。
using
语句不需要显式声明局部变量。也可以使用表达式。
语言规范指定了以下语法。
using_statement
: 'using' '(' resource_acquisition ')' embedded_statement
;
resource_acquisition
: local_variable_declaration
| expression
;
If the form of resource_acquisition is local_variable_declaration then the type of the local_variable_declaration must be either dynamic or a type that can be implicitly converted to System.IDisposable
. If the form of resource_acquisition is expression then this expression must be implicitly convertible to System.IDisposable
.
现有变量的赋值(或丢弃结果)也是一个表达式。例如下面的代码编译:
var a = (_ = 10);
因此,我在开发 UWP 应用程序时经常使用的一种模式是使用 SemaphoreSlim
实例来避免竞争条件(我不喜欢使用 lock
,因为它需要一个额外的目标对象,并且它不会异步锁定)。
典型的代码片段如下所示:
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
public async Task FooAsync()
{
await Semaphore.WaitAsync();
// Do stuff here
Semaphore.Release();
}
随着额外的 try/finally
块围绕整个事情,如果中间的代码可能崩溃但我想保持信号量正常工作。
为了减少样板文件,我尝试编写一个包装器 class,它具有相同的行为(包括 try/finally
位),但需要的代码更少。我也不想使用 delegate
,因为那样每次都会创建一个对象,我只是想减少我的代码而不改变它的工作方式。
我想出了这个 class(为简洁起见删除了评论):
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();
}
}
它的工作方式是,通过使用它,您只需要以下内容:
private readonly AsyncMutex Mutex = new AsyncMutex();
public async Task FooAsync()
{
using (_ = await Mutex.Lock())
{
// Do stuff here
}
}
缩短了一行,并且内置了 try/finally
(using
块),太棒了。
Now, I have no idea why this works, despite the discard operator being used.
丢弃 _ 实际上只是出于好奇,因为我知道我应该写 var _
,因为我需要在 [=20] 的末尾使用 IDisposable
对象=] 块,而不是丢弃器。
但是,令我惊讶的是,两种方法都生成了相同的 IL:
.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
}
"discarder" IDisposable
存储在字段 V_1
中并正确处理。
那么,为什么会这样呢? docs 没有说明丢弃运算符与 using
块一起使用,他们只是说丢弃赋值被完全忽略。
谢谢!
丢弃功能的使用在这里真的是一个转移注意力的问题。之所以可行,是因为 using
语句可以接受解析为要处理的值的表达式(除了声明变量的替代语法之外)。此外,赋值运算符 解析为赋值 。
您在赋值运算符右侧提供的值是您的 Lock 对象,因此这就是表达式 _ = await Mutex.Lock()
解析的内容。由于 value(不是作为变量声明,而是作为独立值)是一次性的,因此它将在 using
结束时清理。
using
语句不需要显式声明局部变量。也可以使用表达式。
语言规范指定了以下语法。
using_statement : 'using' '(' resource_acquisition ')' embedded_statement ; resource_acquisition : local_variable_declaration | expression ;
If the form of resource_acquisition is local_variable_declaration then the type of the local_variable_declaration must be either dynamic or a type that can be implicitly converted to
System.IDisposable
. If the form of resource_acquisition is expression then this expression must be implicitly convertible toSystem.IDisposable
.
现有变量的赋值(或丢弃结果)也是一个表达式。例如下面的代码编译:
var a = (_ = 10);