是否有可能强制使用“using”来处理一次性类?

43

我需要强制使用 "using" 来释放一个类的新实例。

public class MyClass : IDisposable
{
   ...
}

using(MyClass obj = new MyClass()) // Force to use "using"
{
}

2
如果您这样做,资源只能在方法范围内使用。这肯定会使事情变得更容易,但也会显著限制使用。 - Brian Rasmussen
2
@Brian Rasmussen:有时候限制反而能让你更加专注于更重要的事情。 - LBushkin
@Tragedian,一个使用案例是用于模拟其他用户的模拟类。您希望确保在实例未被使用时,模拟将被恢复,并尽快恢复。 - Ray Cheng
1
@denfromufa,所有可用选项已在此列出,因此不确定您想通过赏金实现什么目标。 - Evk
2
@denfromufa 在什么黑暗、深度有缺陷的替代宇宙中,Eric Lippert 不是可信、权威的来源? - Ňɏssa Pøngjǣrdenlarp
显示剩余5条评论
10个回答

50

需要确保对象被释放的事实表明存在设计缺陷。如果释放是礼貌或高效的事情,那么这很好,但它不应该是语义上必要的。

没有办法通过using语句强制执行对象的释放。然而,您可以在对象中维护一个指示对象是否被释放的标志,然后编写一个finalizer来检查该标志。如果finalizer检测到对象未被释放,则可以让finalizer通过failfast终止进程。也就是说,严惩忽略释放对象的用户,迫使他们修复错误或停止使用您的对象。

我认为这并不友善、好或礼貌,但只有您知道未释放对象的可怕后果是什么。对于是否对未遵循您疯狂规则的人施加惩罚比忍受其不遵循规则的后果更好,由您决定。


4
@Eric:很有趣的巧合,但我今天早些时候在你的博客上发布了一个类似的问题。 我想说的是,在处理消耗会对整个过程产生不利影响的非常有限的资源时,释放变得语义上必要。 如果编译器能够强制执行(或至少警告)某些可处置资源未确定性释放时,这将非常有帮助。 为此,我建议在实现IDisposable接口的类型上标记一个属性(例如RequiresRAIISemantics),以便进行标记。 - LBushkin
1
我觉得这个评论很奇怪,因为在大多数情况下,析构函数/终结器只有在代码出现错误时才会运行,而在其他地方,您似乎认为让有缺陷的代码彻底失败比掩盖错误并勉强运行更好。除了共享的不可变对象(例如Bitmap)通过WeakReference缓存是相对安全的情况外,我看不到不释放所有东西的借口(尽管我发现在构造函数抛出异常的对象上调用Dispose的困难非常令人恼火)。 - supercat
1
再次阅读这个答案,让我认为当资源是某种存储时,强制处理资源的释放是一个不好的设计选择。尽管如此,在处理锁时经常使用using语句,提供了一种在锁的上下文中操作资源的清晰语法。在作用域块中强制获取/释放锁又是一种设计选择,但我认为它通常不会被认为是不好的。在这些情况下,没有办法强制执行using语句,必须依赖于API设计的权宜之计。 - ceztko
1
@denfromufa:我觉得这个用例有点奇怪;全局锁的正确使用方式是将其提供给“lock”语句,而不是“using”语句。 - Eric Lippert
1
我认为强制实施RAII语义是一个非常好的设计。 - franckspike
显示剩余4条评论

23

虽然不太美观,但你可以像这样做:

    public sealed class DisposableClass : IDisposable
    {
        private DisposableClass()
        {

        }

        public void Dispose()
        {
            //Dispose...
        }

        public static void DoSomething(Action<DisposableClass> doSomething)
        {
            using (var disposable = new DisposableClass())
            {
                doSomething(disposable);
            }
        }
    }

这是一种非常有趣的延续传递风格(CPS)的应用,但我不知道它是否真的可扩展到获取和释放资源的一般情况。 - LBushkin
这并不丑陋,请将DoSomething()重命名为DoWithDisposableClass()并将其移动到工厂。 - LuckyLikey
如果有多个未知的方法可以代表 doSomething,该怎么办? - denfromufa

18

使用Roslyn框架,您可以编写自己的警告/错误。您的DiagnosticAnalyzer会检查所有构造函数调用,以查看是否正在构造您的类,以及是否在using语句中。

报告的诊断可以设置为错误严重性,并且可以标记为不可配置,这意味着没有人可以将其降级为警告或信息。

此外,如果您正在开发Nuget库,可能希望将分析器作为开发依赖项进行打包,并将其添加为分析器nuget包。这将导致所有用户都必须处理您提供的类。这种打包被称为"代码感知库"

请注意,理论上第三方分析器库也可以执行此操作(例如FxCop),但有许多IDisposable实现不严格需要处理,例如MemoryStream,其Dispose不会有很大作用,因此这些规则要么具有一些白名单机制,要么报告错误的结果。


1
有人为我们做了这个:https://github.com/DotNetAnalyzers/IDisposableAnalyzers - Alex

5
使用语句是编译器从以下内容转换而来的一种简写形式:
(using DisposableObject d = new DisposableObject()){}

into:

DisposableObject d = new DisposableObject()
try
{

}
finally
{
    if(d != null) d.Dispose();
}

所以你的问题大致是是否可能强制编写一个try/finally块,调用对象的Dispose方法。


3
从技术角度来看,应该是 ((IDisposable)d).Dispose(); ;) - Anthony Pegram
它们之间实际上是有区别的。请参阅http://blogs.msdn.com/mhop/archive/2006/12/12/implicit-and-explicit-interface-implementations.aspx,了解隐式和显式接口实现的讨论,其中包括强制转换为接口很重要的示例。 - Brian
@Brian,感谢提供链接。所以它是(IDisposable)d,因为如果Dispose()被显式实现,它可能会对d隐藏,除非将其转换为接口。 - Grokodile
代码可能仍然可以编译,因为也许还存在Dispose的隐式实现。 - Brian

5

我想知道 FXCop 是否能够执行这个规则?


3
是的,有DisposeObjectsBeforeLosingScope规则。请参见:http://blogs.msdn.com/codeanalysis/archive/2010/03/22/what-s-new-in-code-analysis-for-visual-studio-2010.aspx。 - LBushkin

1
不,你不能那样做。你甚至无法强制他们调用dispose函数。你所能做的最好的事情就是添加一个finalizer(析构函数)。只需记住,在对象被处理时,finalizer会被调用,这取决于运行时环境。

1

不可能的。但是您可以在类的终结器中调用dispose方法(如果实际调用dispose方法,您可以禁止使用它),这样如果未在代码中显式地完成,它就会触发。

此链接将向您展示如何实现终结器/释放模式:

http://www.devx.com/dotnet/Article/33167


0
如果您想在范围内强制处理资源,这是可能的,但并不一定需要使用IDisposable。使用以下代码即可实现:
public class ResourceHandle
{
    public delegate void ResourceProvider(Resource resource);

    private string _parms;

    public ResourceHandle(string parms)
    {
        _parms = parms;
    }

    public void UseResource(ResourceProvider useResource)
    {
        Resource resource = new Resource(_parms);
        useResource(resource);
        resource.Close();
    }
}


public class Resource
{
    private string _parms;

    internal Resource(string parms)
    {
        // Initialize resource
    }

    internal void Close()
    {
        // Do cleaning
    }

    // Public methods of resource
}

您只能以以下方式使用资源:

public void foo()
{
    ResourceHandle resourceHandle = new ResourceHandle("parms");

    resourceHandle.UseResource(delegate(Resource resource)
        {
            // use resource
        });
}

正如您所看到的,这里并不真正需要使用IDisposable。


0
你应该了解一下 RAII,这是一种确保所获取的资源将被正确处理的技术。
我的意思是,如果你不能强制调用Dispose方法(通过using或直接调用),你可以把它的内容放在另一个方法里面,比如析构函数。
下面是一种常见的实现IDisposable的模式:
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void Dispose()
{
    Dispose(true);
    // This object will be cleaned up by the Dispose method.
    // Therefore, you should call GC.SupressFinalize to
    // take this object off the finalization queue 
    // and prevent finalization code for this object
    // from executing a second time.
    GC.SuppressFinalize(this);
}

// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the 
// runtime from inside the finalizer and you should not reference 
// other objects. Only unmanaged resources can be disposed.
private void Dispose(bool disposing)
{
    // Check to see if Dispose has already been called.
    if(!this.disposed)
    {
        // If disposing equals true, dispose all managed 
        // and unmanaged resources.
        if(disposing)
        {
            // Dispose managed resources.
            component.Dispose();
        }

        // Call the appropriate methods to clean up 
        // unmanaged resources here.
        // If disposing is false, 
        // only the following code is executed.

        // TODO: write code
    }
    disposed = true;         
}

// Use C# destructor syntax for finalization code.
// This destructor will run only if the Dispose method 
// does not get called.
// It gives your base class the opportunity to finalize.
// Do not provide destructors in types derived from this class.
~ClassName()
{
    // Do not re-create Dispose clean-up code here.
    // Calling Dispose(false) is optimal in terms of
    // readability and maintainability.
    Dispose(false);
}

来源: http://msdn.microsoft.com/zh-cn/library/system.gc.suppressfinalize.aspx


0
如果你想强制使用这个类,你的代码可能需要在其他类中编写并隐藏MyClass以供正常使用。

这样做行不通,因为你的“包装器”类必须实现IDisposable接口才能允许你处理原始类,否则你又回到了最初的状态,使用终结器。 - juharr
不,包装类不需要实现IDisposable接口,它控制MyClass的释放。是的,包装类的Dispose方法是用来释放其子元素(例如MyClass)的,但是他可以在完成工作后再释放MyClass,并且包装类可以在此操作之前长时间存在。 - Svisstack

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