【问题标题】:Is it good practice to use empty using statement to close a disposable object in C#?在 C# 中使用空 using 语句关闭一次性对象是一种好习惯吗?
【发布时间】:2017-11-02 07:58:03
【问题描述】:

我正在开发一个可以发送 ftp 请求的类,它有一个实用方法可以执行不同类型的 ftp 方法:

private FtpWebResponse DoFttpRequest(Uri uri, NetworkCredential credentials, string method, string file = null)
{
    var request = (FtpWebRequest)WebRequest.Create(uri);
    request.Credentials = credentials;
    request.Method = method;

    if (!string.IsNullOrEmpty(file))
    {
        using (var stream = request.GetRequestStream())
        using (var writer = new StreamWriter(stream))
        {
            writer.Write(file);
        }
    }

    return (FtpWebResponse)request.GetResponse();
}

如您所见,此方法执行 ftp 方法并将响应流返回给调用者。下面是使用该方法通过ftp将字符串内容写入文件的客户端方法:

public void WriteToFile(string path, string contents)
{
    var uri = new Uri(path);
    using (var ftpResponse = DoFttpRequest(uri, _credentials, Ftp.UploadFile, contents)) { }
}

如您所见,这里我使用空 using 语句 using (var ftpResponse = DoFttpRequest(uri, _credentials, Ftp.UploadFile, contents)) { } 来处理接收到的流。 这是处理这样的对象的好方法吗?是否有必要处置这个流,因为它可能会被垃圾收集器处置?

【问题讨论】:

  • 如果您不打算使用它,为什么还需要引用它?我只想写DoFttpRequest(new Uri(path), _credentials, Ftp.UploadFile, contents)
  • 需要处理掉。而不是使用你可以做DoFttpRequest(..).Dispose()。如果你使用空使用 - 不需要声明变量:using (DoFtpRequest(...)) {}.
  • 在我看来,这个问题与ftpwebrequest 无关,一般来说更多的是using
  • 使用 = 尝试 { 不管 } 最后 { 处置 }。使用 using... 但将其与 { } 一起使用,以便清楚发生了什么。
  • @Marco 这是一个常见的约定,如果你有多个链接,你只使用一组括号。

标签: c# ftp dispose using webrequest


【解决方案1】:

是否有必要处置此流,因为它可能会 反正由垃圾收集器处理

您可以使用这个简单的代码来查看不处理响应流可能会如何完全破坏应用程序。我使用 http 请求而不是 ftp 来简化测试,但这同样适用于 ftp 请求。

public class Program {
    static void Main(string[] args) {
        // this value is *already* 2 by default, set for visibility
        ServicePointManager.DefaultConnectionLimit = 2;
        // replace example.com with real site
        DoFttpRequest("http://example.com");
        DoFttpRequest("http://example.com");
        DoFttpRequest("http://example.com");
        Console.ReadLine();
    }

    private static HttpWebResponse DoFttpRequest(string uri) {
        var request = (HttpWebRequest) WebRequest.Create(uri);
        var response = (HttpWebResponse) request.GetResponse();
        Console.WriteLine("got response");
        return response;
    }
}

请注意,您不会处理 HttpWebResponse。将会发生的情况是您将在控制台中看到 2 条“得到响应”消息,然后应用程序将挂起尝试第三次获得响应。这是因为每个端点(每台主机)的并发连接限制为 2,因此虽然与主机(此处为 example.com)的 2 个连接“正在进行” - 与同一主机的下一个连接必须等待它们完成。因为您不处理响应 - 在 GC 收集它们之前,这些连接不会“完成”。在那之前 - 您的应用程序挂起,然后因超时而失败(如果 request.Timeout 设置为某个合理的时间)。所有后续请求也会挂起,然后因超时而失败。如果您处理响应 - 应用程序将按预期工作。

因此,请务必丢弃一次性物品。不需要使用块,你可以做DoFtpRequest(..).Dispose()。但是,如果您更喜欢空使用 - 至少不要声明不必要的变量,只需执行 using (DoFttpRequest(..)) {}。在空使用和Dispose 之间进行选择时要注意的一件事是DoFtpRequest 返回null 的可能性,因为如果它返回null - 显式Dispose 将抛出NullReferenceException 而空使用只会忽略它(你可以这样做DoFttpRequest(...)?.Dispose(); 如果您期望 null 但不想使用 using)。

【讨论】:

  • 如果抛出异常,将不会调用Dispose()。除非您将调用包装到 try-finally 块中并在那里调用它。这正是 using 块为您所做的。
  • @thehennyy 但这里 using 块是空的。如果在DoFttpRequest 内部发生异常 - 没有什么可处置的,还没有返回任何值。当我们返回值时 - 我们会立即处理它。
【解决方案2】:

using 语句实际上是执行某种代码,然后简单地调用 Dispose 方法。 这就是为什么你只能使用它的类型继承自 IDisposible 接口(在大多数情况下)

因此您实际上不必使用 using 语句。只需调用

DoFttpRequest(uri, _credentials, Ftp.UploadFile, contents)).Dispose()

如果您不自行处置和反对,垃圾收集器会在范围完成后自动处置它。 当您使用 c#、java 等高级语言时,您不必过多考虑内存......它们被称为 内存管理语言。他们会为您处理这类员工。

【讨论】:

  • 并非如此。如果该方法抛出异常,则不会被释放。这就是为什么他应该使用“使用”
  • "垃圾收集器在作用域完成后自动处理它" 不正确,GC 只能处理托管资源。实施IDisposable 仅对非托管 资源有意义。您必须致电Dispose,GC 不会为您这样做。
  • "但是就像我说的,如果你终止进程 finally 块不会被调用" 是的,但是如果你在尝试自己调用 Dispose 的那一刻杀死它,它肯定不会被调用也不会被处置。这与您调用Diposeself 或使用using-statement 无关。然而,杀死一个进程并不是 IMO 问题的一部分。
  • 但是 GC 不会调用 Dispose。这就是using-statement 的全部意义所在。见stackoverflow.com/a/45049/2528063
  • 如果方法抛出异常,它不会返回任何东西,因此没有任何东西可以处理。然而,需要处理的是该方法可能返回null,而using 将处理它,因此正确的调用将是?.Dispose()。除此之外还好。
猜你喜欢
  • 2013-03-30
  • 2015-02-18
  • 2016-03-20
  • 1970-01-01
  • 2013-08-03
  • 2018-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多