是否存在不依赖于IDisposable的使用模式?

时间:2009-05-01 18:06:21

标签: c# idisposable using misuse

我想创建一个内部消息系统,可以告诉我一些代码被调用的持续时间。我在考虑易用性,以使SystemMessage类实现IDisposable。

我会在SystemMessage的构造函数中设置一个时间戳,如果调用了Dispose,我可以计算出持续时间。

问题是我不想让对象GC'。我希望它作为MessageCollection的一部分留在身边。

C#中是否有另一个构造可以让我在不使用IDisposable的预期功能的情况下使用Using语句的可用性。

Using (message = Collection.CreateNewMessage("FileDownlading"))
{
    // I wonder how long it is taking me to download this file in production?
    // Lets log it in a message and store for later pondering.
    WebClass.DownloadAFile("You Know This File Is Great.XML");
}
// we fell out of the using statement, message will figure out how long
// it actually took to run.
// This was clean and easy to implement, but so wrong?

12 个答案:

答案 0 :(得分:5)

  

问题是我不想让对象GC'。我希望它作为MessageCollection的一部分留在身边。

调用Dispose不会导致对象被GC控制 - 当GC执行扫描并且没有任何内容引用它时会发生这种情况。如果您仍然通过MessageCollection引用该对象,它将会坚持下去。

Dispose可以防止它被终结,但由于你没有使用Dispose来清理资源,你将没有Finalizer,你也不会关心。

所以,真正唯一的问题是令人困惑的语义围绕让你的calss实现IDisposable,即使没有资源可以处理。

就个人而言,我不认为这是一个问题。如果消费者呼叫Dispose,那么很好 - 他们得到时间记录。如果他们不这样做,那么他们就不会获得itme邮票,而最糟糕的情况是他们会遭到FxCop违规。

然而,这有点不直观 - 所以如果这是供公众使用的话,我建议提供一个更可发现的替代方案,如:

// C# 3+ lambda syntax
Collection.CreateNewMessage("FileDownlading", () => {
    // I wonder how long it is taking me to download this file in production?    
    // Lets log it in a message and store for later pondering.    
    WebClass.DownloadAFile("You Know This File Is Great.XML");
});

// C# 2 anonymous delegate syntax
Collection.CreateNewMessage("FileDownlading", delegate() {
    // I wonder how long it is taking me to download this file in production?    
    // Lets log it in a message and store for later pondering.    
    WebClass.DownloadAFile("You Know This File Is Great.XML");
});

// Method
void CreateNewMessage(string name, Action action) {
   StopWatch sw = StopWatch.StartNew();
   try {
      action();
   } finally {
      Log("{0} took {1}ms", name, sw.ElapsedMilliseconds);
   }
}

将运行并为Action代理计时。

答案 1 :(得分:2)

你在寻找类似于闭合的东西吗?

http://en.wikipedia.org/wiki/Closure_(computer_science)

你可以伪装一些......就像这样...

private TimeSpan GetDuration(Action a)
        {
            var start = DateTime.Now;
            a.Invoke();
            var end = DateTime.Now;
            return end.Subtract(start);
        }

        public void something()
        {
            string message;
            var timeSpan = GetDuration(() => { message = "Hello"; } );
        }

答案 2 :(得分:1)

我认为使用不是你想要的。为什么不让构造函数记录时间,然后在调用DownloadAFile时记录时间增量?在您的示例中,如果存在异常,则会记录异常的时间,就像下载文件一样。

如果您确实希望行为与您的示例类似,只需使用try / finally块,并在finally中执行日志记录。 using只是try / finally块的语法糖和对Dispose的调用。

示例的等效代码如下所示:

try 
{
    var message = Collection.CreateNewMessage("FileDownlading"); 
    //...
    WebClass.DownloadAFile("You Know This File Is Great.XML");
}
finally 
{
    //You can change this to do logging instead.
    message.Dispose(); 
}

答案 3 :(得分:1)

您可以使用DownloadAFile()内的System.Diagnostics.Stopwatch在每次调用时进行计时。

或者,只需在对DownloadAFile()的调用周围添加秒表代码(取决于您希望如何工作)。

在这种情况下使用IDisposable不是一个好主意。

答案 4 :(得分:1)

我最近一直在看这个,也许PostSharp可以帮到你。它允许您使用在方法启动和停止时调用的属性来修饰方法。

http://www.postsharp.org/

我不确定它会做你喜欢的,但它值得调查,它有你想要的“语法糖”!

克里斯

答案 5 :(得分:1)

嗯,这是一个老人,但似乎没有其他人发布我认为最好的风格(根据我对你的要求的理解):

using (MessageTimer timer = Collection.CreateNewTimedMessage("blabla"))
{
   // ...
}

重要的一点是,CreateNewTimedMessage可能作为副作用创建并存储半永久Message对象,但返回临时计时对象(使用StopWatch,或者某种类似的机制),它不能在using块的范围内存活。 (MessageTimer可以引用Message,但不能反过来。)

因此处置MessageTimer对象可能会产生将最终时间记录到某处的副作用,但对象本身将无法生存或复活;这不是滥用using构造,因为你真的在处理一个对象。

MessageTimer的实际实现可能类似于Joe的回答。)

答案 6 :(得分:0)

不是真的。你最接近的就是这样的东西(无论如何,这是在using()语句的引擎下发生的事情:)

var message = Collection.CreateNewMessage("FileDownloading")

try
{
    WebClass.DownloadAFile("You Know This File Is Great.XML");
}
finally
{
    message.HowLongHaveIBeenAlive();
}

答案 7 :(得分:0)

using语句用于在完成对象后处理它们。在物体被处理后保持物体的想法并不是完全有意的。您可以尝试做一些简单的事情:

message = Collection.CreateNewMessage("FileDownlading");
DateTime dtStart = DateTime.Now;
WebClass.DownloadAFile("You Know This File Is Great.XML");
DateTime dtEnd = DateTime.Now;

// perform comparison here to see how long it took.

// dispose of DateTimes
dtStart = dtEnd = null;

答案 8 :(得分:0)

最初IDisposable旨在通过C#引入确定性清理,但我看到authorsimplementations使用using / {{1}根据你所谈论的内容,提供某些功能。

就个人而言,我对此并不太满意,因为它破坏了Dispose背后的概念,但由于没有替代方案,所以它归结为你想要的正统性问题。我当然可以理解你为什么要这样做,但这样做会让你更难解释IDisposable接口的目的和重要性。

答案 9 :(得分:0)

可以说这是对使用构造的滥用,所以我可能不会在以这种方式使用IDisposable的共享类库中实现公共类。

但是我已经看到了这样的事情,并认为如果它保留在你的应用程序内部就可以了。

  

问题在于我不想   有GC的对象。我想要它   留在周围作为一部分   MessageCollection。

我根本不明白这一点。 IDisposable与GC无关,如果将其作为MessageCollection的元素引用,您的消息将保持活动状态。

您的邮件类可能类似于下面的示例。在调用Dispose之后,它仍然活着并且很好。调用Dispose后,实现IDisposable的大多数类都不可用,因此如果在调用Dispose后访问成员,则它们的实现会抛出ObjectDisposedException。但这绝不是强制性的。

class Message : IDisposable
{
    private Stopwatch _stopwatch = Stopwatch.StartNew();
    private long _elapsedTicks;
    private string _message;

    public Message(string message)
    {
        _message = message;
    }

    public void Dispose()
    {
       _elapsedTicks = _stopwatch.ElapsedTicks;
       ... anything else including logging the message ...
    }

    ...
}

答案 10 :(得分:0)

'using'语句实际上编译为Try / Finally,期望对象实现IDisposable - “using”只是语言提供的快捷方式。

出于您的目的,特别是因为您想要回收对象,我会考虑编写您自己的界面。当你调用一个方法来开始计时时,只需获取datetime.now的值;当你停止计时器时然后从停止时间中减去开始时间,你就会有持续时间。

避免在每个类实例上使用真正的Timer对象。 Timer使用ThreadPool线程,这意味着每条消息将消耗至少一个线程 - 如果系统中有太多消息,则应用程序将因线程切换而变慢。此外,如果计时器没有正确处理,他们可能不会释放他们的ThreadPool线程,你基本上会有线程泄漏。

答案 11 :(得分:0)

在再次审核您的问题之后,我没有看到有问题的对象是如何真正的消息,除非它更像是跟踪消息(用于调试帮助)。

如果您正在寻找更多内容,那么这里是使用委托的非常粗略方法。基本上,您为要调用的每个方法和时间创建一个委托,然后将委托和方法参数传递给负责实际调用方法并计时其持续时间的帮助程序。

我的例子中明显的缺点是我牺牲了类型安全的参数,因为我不确定它是否正是你正在寻找的。另一个问题是,每次要调用具有您尚未拥有委托方法签名的方法时,您需要添加新委托:

using System;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {

            SomeCaller callerOne;
            YetAnotherCaller callerTwo;

            callerOne = new SomeCaller(SomeMethod);
            LogCallDuration(callerOne, new object[] { 15 });

            callerOne = new SomeCaller(SomeOtherMethod);
            LogCallDuration(callerOne, new object[] { 22 });

            callerTwo = new YetAnotherCaller(YetAnotherMethod);
            LogCallDuration(callerTwo, null);

            Console.ReadKey();
        }

        #region "Supporting Methods/Delegates"

        delegate void SomeCaller(int someArg);
        delegate void YetAnotherCaller();

        static void LogCallDuration(Delegate targetMethod, object[] args)
        {
            DateTime start = DateTime.UtcNow;
            targetMethod.DynamicInvoke(args);
            DateTime stop = DateTime.UtcNow;

            TimeSpan duration = stop - start;

            Console.WriteLine(string.Format("Method '{0}' took {1}ms to complete", targetMethod.Method.Name, duration.Milliseconds));

        }

        #endregion "Supporting Methods/Delegates"

        #region "Target methods, these don't have to be in your code"
        static void SomeMethod(int someArg)
        {
            // Do something that takes a little time
            System.Threading.Thread.Sleep(1 + someArg);
        }

        static void SomeOtherMethod(int someArg)
        {
            // Do something that takes a little time
            System.Threading.Thread.Sleep(320 - someArg);
        }

        static void YetAnotherMethod()
        {
            // Do something that takes a little time
            System.Threading.Thread.Sleep(150);
        }
        #endregion "Target methods"
    }
}