我可以在using块中清除外部变量吗?

4
我有一个静态的StreamWriter字段,用于记录文件,我需要通过监听长时间运行进程的StandardOutput的lambda函数访问它。
我使用该字段的null / not-null状态来确定进程是否在另一线程上忙碌;需要按顺序执行操作。
我的问题是,在using块内将变量设置为null时会发生什么? 它仍然会被正确处理吗?
public class Service
{
    private static StreamWriter logger;

    void Run(string logFile)
    {
        using (logger = new StreamWriter(logFile))
        {
            /* ... */

            logger = null;
        }
    }
}

它可以工作,但不建议这样做。 - H H
它可能不是线程安全的。 - H H
但是在内部,编译器是否对using()括号内的对象持有不同类型的引用?还是它记住了我的字段名称,并在准备调用Dispose()时反对它? - J Bryan Price
只有一种引用,using(){} 只是创建一个副本。 - H H
我的错误;显然是同样的“类型”引用。我应该把那个短语省略掉。 - J Bryan Price
3个回答

3
根据C#参考文献,§ 8.13,您的代码:
private static StreamWriter logger;

using (logger = new StreamWriter(logFile))
{
   /* ... */
   logger = null;
}

等同于

private static StreamWriter logger;


{  // using scope
   logger = new StreamWriter(logFile);
   IDisposable resource = logger;       // hidden var inserted by the compiler 
   try
   {
     /* ... */
     logger = null;
   }
   finally
   {
      if (resource != null)  
        resource.Dispose();
   }
}

相关引用:

形式为 using 语句

    using (expression) statement

具有相同的三种可能扩展,但在这种情况下,ResourceType隐式地是表达式的编译时类型,并且资源变量在嵌入语句中不可访问且不可见。

2
当我在using代码块内将变量设置为null时,会发生什么?它是否会被正确地处理?
这取决于变量的声明位置。如果变量在using语句之外声明,那么它将被正确处理或者你的代码一开始就不会编译。
如果你的变量是在using语句之外声明的:是的。
A a;
using (a = new A())
{
    a = null;
}

是的,它将被正确处理。进行简单测试:

在using块中将变量置为null并成功释放
我的文本颜色方案为Ragnarok Grey

即使清除了赋值,似乎仍然保留了对new A()的引用。 a会按预期在using语句的末尾被处理,即使它在内部被置为null。

在某些版本的Visual Studio中,这可能会导致编译器警告(级别2)CS0728

可能有误的赋值给局部变量'a',该变量是using或lock语句的参数。 Dispose调用或解锁将在局部变量的原始值上发生。

如果你的变量在using语句中声明:不适用

using (var a = new A())
{
    a = null;
}

上述代码无法编译。首先,您不允许对 using 变量进行赋值操作。上述代码会产生以下编译错误:

无法将值分配给 'a',因为它是一个 'using 变量'

这是 编译器错误 CS1656

当在只读上下文中对变量进行赋值操作时,会出现此错误。只读上下文包括 foreach 迭代变量、using 变量和 fixed 变量。要解决此错误,请避免在using块、foreach语句和fixed语句中对语句变量进行赋值操作。


1
好答案。这些编译器的作者们……他们真是一群聪明的家伙! - spender
但是Op在using语句之前声明了logger。他的代码将只编译出一个警告。 - H H
@Henk编辑。感谢您指出这一点。我没有注意到,哇。两者有非常不同的结果。 - doppelgreener
我在编译中也没有看到警告。我的静态 logger 在声明中是预定义的(我没有展示),并且被赋予了 null 值。 - J Bryan Price
@Henk 谢谢,我在我的答案中提到了这一点。似乎较新版本的Visual Studio可能不会产生该警告! - doppelgreener
显示剩余4条评论

2

这不是问题。您需要使用ildasm.exe查看IL代码,以了解编译器的处理方式。但它会生成一个额外的变量来存储对象的引用,因此您不能像这样错误地访问。相应的C#代码大致如下:

StreamWriter $temp = new StreamWriter(logFile);
logger = $temp;
try {
   // etc...
   logger = null;
}
finally {
   if ($temp != null) $temp.Dispose();
}

那个额外的 $temp 变量可以帮助你避免麻烦。

+1 很棒的答案。谢谢你解释为什么它是这样工作的! - doppelgreener
谢谢!它真的在将我的IDisposable引用分配给变量之前抓取它吗?(只是一个旁边的问题) - J Bryan Price
是的,确实如此。不要犹豫,尝试使用ildasm.exe。 - Hans Passant
有趣的是:我正在查看ILSpy反编译,它似乎先设置日志记录器,然后再设置隐藏的本地变量。我会预期这样,因为我的using语句可能有更复杂的IDisposable生成表达式。但还是谢谢你指导我查看IL代码。 - J Bryan Price
顺便说一句,我完全有能力自己搞砸。只是显然不是这种方式 :) - J Bryan Price

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接