简体   繁体   English

为什么C#7丢弃标识符_仍在使用块中?

[英]Why does the C# 7 discard identifier _ still work in a using block?

So, a pattern I use very often while working on my UWP app is to use a SemaphoreSlim instance to avoid race conditions (I prefer not to use lock as it needs an additional target object, and it doesn't lock asynchronously). 因此,我在使用UWP应用程序时经常使用的模式是使用SemaphoreSlim实例来避免竞争条件(我不想使用lock因为它需要一个额外的目标对象,并且它不会异步锁定)。

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 the additional try/finally block around the whole thing, if the code in between could crash but I want to keep the semaphore working properly. 使用额外的try/finally块围绕整个事情,如果中间的代码可能崩溃但我想保持信号量正常工作。

To reduce the boilerplate, I tried to write a wrapper class that would have the same behavior (including the try/finally bit) with less code needed. 为了减少样板,我尝试编写一个包含类的包装类,它具有相同的行为(包括try/finally位),所需的代码更少。 I also didn't want to use a delegate , as that'd create an object every time, and I just wanted to reduce my code without changing the way it worked. 我也不想使用delegate ,因为每次都会创建一个对象,我只是想减少我的代码而不改变它的工作方式。

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 the way it works is that by 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 shorter, and with try/finally built in ( using block), awesome. 一条线更短,并且使用try/finally内置( using块),非常棒。

Now, I have no idea why this works, despite the discard operator being used. 现在,尽管使用了discard运算符,但我不知道为什么会这样。

That discard _ was actually just out of curiosity, as I knew I should have just written var _ , since I needed that IDisposable object to be used at the end of the using block, and not discarder. 丢弃_实际上只是出于好奇,因为我知道我应该写var _ ,因为我需要在using块的末尾使用IDisposable对象,而不是discarder。

But, to my surprise, the same IL is generated for both methods: 但令我惊讶的是,两种方法都生成了相同的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    
}

The "discarder" IDisposable is stored in the field V_1 and correctly disposed. “discarder” IDisposable存储在字段V_1并正确处理。

So, why does this happen? 那么,为什么会这样呢? The docs don't say anything about the discard operator being used with the using block, and they just say the discard assignment is ignored completely. 文档没有说明与using块一起using的丢弃运算符,他们只是说完全忽略了丢弃分配。

Thanks! 谢谢!

The using statement does not require an explicit declaration of a local variable. using语句不需要显式声明局部变量。 An expression is also allowed. 表达式也是允许的。

The language specification specifies the following syntax. 语言规范指定以下语法。

 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 . 如果resource_acquisition的形式是local_variable_declaration,则local_variable_declaration的类型必须是动态的,或者是可以隐式转换为System.IDisposable If the form of resource_acquisition is expression then this expression must be implicitly convertible to System.IDisposable . 如果resource_acquisition的形式是表达式,则此表达式必须可隐式转换为System.IDisposable

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

An assignment of an existing variable (or discarding the result) is also an expression. 分配现有变量(或丢弃结果)也是一种表达方式。 For example the following code compiles: 例如,以下代码编译:

var a = (_ = 10);

The use of the discard feature is really a red herring here. 丢弃功能的使用在这里真的是一个红鲱鱼。 The reason that this works is because the using statement can accept an expression that resolves to a value to be disposed (in addition to an alternate syntax that declares a variable). 这样做的原因是因为using语句可以接受解析为要处理的值的表达式(除了声明变量的替代语法之外)。 Additionally, the assignment operator resolves to the value that is assigned . 此外,赋值运算符解析为指定的值

The value that you're providing on the right hand side of the assignment operator is your Lock object, so that's what the expression _ = await Mutex.Lock() resolves to. 您在赋值运算符右侧提供的值是您的Lock对象,因此表达式_ = await Mutex.Lock()解析为。 Since that value (not as a variable declaration, but as a stand alone value) is disposable, it is the thing that will be cleaned up at the end of the using . 由于该 (而不是作为一个变量声明,但作为一个单独的值)是一次性的,它是将在年底被清理的东西using

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM