在Visual Studio中如何识别一次性对象?

31

建议使用using语句或调用Dispose()方法来处理IDisposable对象。在Visual Studio中,很难直观地了解某个对象是否是可释放的。

我的问题是:有没有办法在VS中识别IDisposable对象?


3
为什么不直接检查你使用的类是否实现了 IDisposable 接口,并使用 using 语句来实例化它们呢? - Alejandro
2
智能感知会告诉您是否已实现dispose方法。 - chris-crush-code
我发现在VS中查看已实现的接口不如Java IDE直观。也许是因为我对VS还不熟悉! - T D Nguyen
@chris-crush-code 在大多数情况下是正确的,但您也可以在创建对象时放置一个Dispose()方法,而无需实现IDisposable - krillgar
@mxmissile 注意,我已经回答了这个问题(https://dev59.com/IlcP5IYBdhLWcg3w6-IH#43636775)。 - TheLethalCoder
显示剩余2条评论
9个回答

29
如果您想在VS中以不同方式突出显示一次性对象,请查看此帖子。我个人更喜欢Resharper 答案,因为我总是使用R#。
如果您只想弄清楚您的对象是否是某个接口的实例,则可以右键单击变量名并导航至“对象浏览器”或“转到声明”,然后右键单击类名“转到定义”/“峰值定义”。

enter image description here

您可能会喜欢Peek Definition,因为它可以在一行内显示您需要的所有内容:

enter image description here

如果一个对象有Dispose()方法,你总是可以检查它具有哪些方法,那么99.9%的情况下它是可处理对象。但是还有0.01%的情况是由于方法命名不规范造成的。


不要忘记你的0.01%中实现了IDisposable接口。但大多数情况下,这些类型提供了一个Close()方法。 - Jehof

24
我很惊讶其他人还没有提到这个。如果您的Visual Studio版本支持它,我建议在构建时打开代码分析
完成后,选择任何规则集,只要它们确保至少涵盖CA2000(在失去作用域之前处理对象),CA2213(可处理字段应该被处理)和CA2202(不要多次处理对象)规则。这样,编译器就会在您没有正确处理可处理对象时警告您。
(尽管请注意,使编译器标记某些可处理对象的使用可能会成为更大的挑战,正如许多StackOverflow问题所证明的那样)。

你能编辑一下这些规则是什么吗?我不知道 CA2213 是什么,而且我不应该去外部链接查找。 - TheLethalCoder
1
@TheLethalCoder - 我已经编辑了规则名称。我认为进一步添加内容没有太大价值。我会链接到完整的文档,即使链接失效,我也会提供代码分析代码,这些应该是稳定的。 - Damien_The_Unbeliever
提示:您可能需要在Visual Studio中安装Microsoft Code Analysis插件或将Nuget包安装到您的项目中。您可能想要转到“项目”>“引用”>“分析器”>“打开活动规则集”>(进行更改)>“另存为”>(文件在解决方案文件夹中)。然后,您需要更新csproj以使用新保存的规则集。您可能还需要VS Enterprise? - Andy Joiner

8
你可以使用对象浏览器查看类继承层次结构和实现的接口。 enter image description here

6
为了完整起见,如果您不是在代码中询问如何检查它,而只是想知道如果类型实现了像IDisposable这样的接口,您可以随时查看MSDN
例如FileStream
  • 它已经在备注部分提到:

此类型实现了IDisposable接口。当您使用完该类型后,应直接或间接地处理它。要直接处理它,请在try/catch块中调用其Dispose方法。要间接处理它,请使用语言构造,如using(在C#中)或Using(在Visual Basic中)。有关详细信息,请参阅IDisposable界面主题中的“使用实现IDisposable的对象”一节。

  • or search for the Dispose method. There you can see if this class or any parent class implements IDispable. In this case it is inherited from Stream which implements that interface which is mentioned at the class syntax and in the remarks section.

    public abstract class Stream : MarshalByRefObject, IDisposable
    

如果你想知道如何在Visual Studio中查找接口的实现,这里已经有一个回答了:

如何查找接口的所有实现?


我可以在Java中很容易地找到接口(不到1秒),但我发现在C#中很难看到它。搜索IDisapsable要慢得多。 - T D Nguyen
@DavidG:在Java文档中,你可以直接在类型的顶部看到所有已实现的接口:https://docs.oracle.com/javase/7/docs/api/java/io/FileReader.html - Tim Schmelter
@ScottChamberlain:是的,但我缺少MSDN中给定类型的所有已实现接口列表。您必须单击继承层次结构中的所有类。 - Tim Schmelter
@TimSchmelter 确实,获取该列表的一种简单方法是在 Visual Studio 中执行“导航到元数据”,它会在该视图中显示继承的接口。它还具有在任何类型上工作而不仅仅是框架类型的额外好处。 - Scott Chamberlain
@TimSchmelter 是的,但OP暗示这是一个IDE问题,而不是文档问题。此外,文档只对框架类型有帮助,而不是自定义类型。 - DavidG
显示剩余4条评论

5

要查看一个类实现了哪些接口,以及该类公开的所有字段、属性、方法等,可以进入该类的代码。例如:

Image image = Image.FromFile(path);

请确保您点击的是类,而不是实例,并按下 F12 键。这将带您到该类的元数据文件。例如:Image.cs 文件在类声明之前有以下内容:

public abstract class Image : MarshalByRefObject, ISerializable, ICloneable, IDisposable

您可以使用F12单击转到其他类。请注意,这些类通常在Visual Studio中以浅蓝色显示:Screenshot showing light blue colours. 您还可以通过右键单击该类并从下拉列表中选择“转到定义”来访问此元数据文件。
尽管不是很理想,但您也可以转到类的实例并在末尾加上一个 .。这应该会带出智能感知并且如果该项实现了接口,则您将能够在列表中看到Dispsose()
您还可以直接编写myInstance.Dispose();using (myInstance = new MyClass()) {},如果代码编译,则该类实现了接口,否则没有。

5
作为Resharper等工具的替代品,Visual Studio有“外部工具”的概念(在“工具”菜单中),您可以滥用它来执行以下操作:

  • 标题: Is Disposa&ble
  • 命令: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
  • 参数: -Command "&{$i=[Type]::GetType('System.IDisposable');[AppDomain]::CurrentDomain.GetAssemblies()|%{ $_.GetTypes()}|?{$_.FullName.EndsWith('.$(CurText)')}|%{New-Object PSObject -Property @{'Type'=$_;'IDisposable'=$i.IsAssignableFrom($_)}}|ft}"
  • 使用输出窗口: 已选中

这将读取您在编辑器中选择的任何字符串,搜索名称包含该字符串的.NET类型,并显示一个True/False,表示该字符串是否实现了IDisposable。

工具中的Powershell命令只是我能够演示这种可能性的最快方法,但它远非完美--它仅查找Powershell默认加载的程序集中的类型。如果您想扩展这个想法,您可以构建一个命令行.NET应用程序,加载您的项目并扫描您的项目加载的所有程序集。

例如,如果您在代码中突出显示单词Stream,并运行您的外部工具(示例中为ALT+TALT+B),它将返回:

Type             IDisposable
----             -----------
System.IO.Stream        True

来分解Powershell命令:

&{ $i=[Type]::GetType('System.IDisposable');        # Get the IDisposable interface
   [AppDomain]::CurrentDomain.GetAssemblies() `     # Get all loaded assemblies
    | %{ $_.GetTypes() } `                          # For each assembly, get all types
    | ?{ $_.FullName.EndsWith('.$(CurText)') } `    # Filter types that are named $(CurText) - $(CurText) is a macro within VS External Tools
    | %{ New-Object PSObject -Property @{           # For each type, return an object containing...
         'Type' = $_;                               # ...the type name...
         'IDisposable' = $i.IsAssignableFrom($_)    # ...and whether the IDisposable interface is implemented
       } } `
    | ft }                                          # Format all returned objects as a table

就像你所说的,这段PowerShell代码片段可以进行一些改进,但作为概念验证的起点,这很棒,不是愚蠢的。在初始设置之后,这应该是最快的确定所选文本是否实现了IDisposable接口的方法,而无需第三方工具或手动遍历层次结构。加一! - Lance U. Matthews

2
通过使用代码分析中的内存警告,最简单的方法是找出这个问题。
1.在项目属性中启用“在构建时运行”代码分析。 enter image description here 2.在构建后查看以下警告。 CA2000 创建了一个IDisposable类型的本地对象,但在所有对该对象的引用超出范围之前,该对象未被处理。

CA2213

实现 System.IDisposable 接口的类型声明了字段,这些字段的类型也实现了 IDisposable 接口。在声明类型的 Dispose 方法中不会调用字段的 Dispose 方法。

enter image description here


-1

在Dispose()方法中使用using。 如果可以的话,最好使用using结构。这是原因。使用这个MSDN的例子,下面是两个等价的代码块。这个用了using

using (Font font1 = new Font("Arial", 10.0f)) 
{
    byte charset = font1.GdiCharSet;
}

这个没有使用

{
    Font font1 = new Font("Arial", 10.0f);
    try
    {
        byte charset = font1.GdiCharSet;
    }
    finally
    {
        if (font1 != null)
            ((IDisposable)font1).Dispose();
    }
}

第二个块的每个部分都有其存在的原因:花括号、try-finally、null 检查和转换为 IDisposable。没有人应该被期望记住这些。这就是为什么 using 结构存在的原因。

1
我认为问题不在于为什么要使用 using/Dispose(),而是如何识别哪些对象实现了 IDisposable 接口,因此应该使用 using/Dispose() 进行处理。换句话说,你说“尽可能使用 using 结构”,而问题是“在 Visual Studio 中,我如何知道‘何时可以’?” - Lance U. Matthews

-3

在C#7中可以完成一些干净的事情

class Foo : IDisposable {
    string _bar = "init";
    void Fooy() { _bar = "fooy"; }

    public void Dispose() {
        _bar = null;       
    }

    static void Main()
    {
        var v = new Foo();
        Console.WriteLine(v._bar);
        if(v is IDisposable id) using(id)
            v.Fooy();
        else
            v.Fooy();

        Console.WriteLine(v._bar ?? "null");;

    }
}

Fooy 可能是一些虚拟或抽象函数。一些基类可能会实现 IDisposable,而其他则不会。尝试运行上面的代码。控制台将根据是否实现了 IDisposable 打印不同的文本。


v.Fooy() 还是 v.Fooy()? 这是打字错误吗? - 15ee8f99-57ff-4f92-890c-b56153
3
我认为在 if 语句的两侧调用相同方法是“正确”的(尽管我不认为它很“干净”)。这个想法是无论 v 是否实现了 IDisposable 接口都调用 v.Fooy() 方法,然后如果它确实实现了 IDisposable 接口,则通过 using 对象释放 v。也许将 using 翻译成 try/finally 可以更清晰地表达:try { v.Fooy(); } finally { if (v is IDisposable id) id.Dispose(); },但这仍然看起来像是一个 IDE/文档问题的运行时解决方案。 - Lance U. Matthews
你说得对,BACON。那就是处理它的方式。我只是在玩一些CS7代码。 - John ClearZ
@BACON 啊,现在我明白了。目的只是为了把 using(id) 放进去。谢谢。 - 15ee8f99-57ff-4f92-890c-b56153

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