代码之家  ›  专栏  ›  技术社区  ›  Sergio0694

为什么C#7 discard identifier在using块中仍然有效?

  •  8
  • Sergio0694  · 技术社区  · 7 年前

    因此,我在使用UWP应用程序时经常使用的一种模式是使用 SemaphoreSlim 实例以避免竞争条件(我不喜欢使用 lock 因为它需要一个额外的目标对象,并且不会异步锁定)。

    典型的代码片段如下所示:

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

    附加的 try/finally 如果介于两者之间的代码可能会崩溃,我会阻止整个过程,但我希望保持信号量正常工作。

    为了简化样板,我尝试编写一个具有相同行为的包装器类(包括 尝试/最终 位),所需代码更少。我也不想用 delegate ,因为这样每次都会创建一个对象,我只是想减少代码,而不改变它的工作方式。

    我想出了这个类(为了简洁起见,删除了注释):

    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
        }
    }
    

    短一行,带 尝试/最终 内置的( using 方块),真棒。

    现在,我不知道为什么会这样,尽管discard操作符 习惯于

    那次丢弃实际上只是出于好奇,因为我知道我应该写 var _ ,因为我需要它 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    
    }
    

    “丢弃者” IDisposable可识别 存储在字段中 V_1 并正确处理。

    那么,为什么会发生这种情况?这个 docs 不要说discard操作符与 使用 块,他们只是说放弃分配被完全忽略。

    谢谢

    2 回复  |  直到 7 年前
        1
  •  7
  •   Wazner    7 年前

    这个 using 语句不需要显式声明局部变量。还允许使用表达式。

    语言规范指定了以下语法。

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

    如果资源获取的形式是local\u variable\u声明,则local\u variable\u声明的类型必须是动态的或可以隐式转换为的类型 System.IDisposable . 如果resource\u acquisition的形式是expression,则此表达式必须隐式转换为 系统IDisposable可识别 .

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

    现有变量的赋值(或放弃结果)也是一个表达式。例如,编译以下代码:

    var a = (_ = 10);
    
        2
  •  5
  •   Servy    7 年前

    在这里,使用discard特性真的是一种转移注意力的方法。之所以这样做是因为 using 语句可以接受解析为要释放的值的表达式(以及声明变量的备用语法)。此外,赋值运算符 解析为指定的值 .

    赋值运算符右侧提供的值是锁定对象,因此表达式 _ = await Mutex.Lock() 解析为。从那以后 价值 (不是作为变量声明,而是作为独立值)是一次性的,它是在 使用 .