是否有不依赖 IDisposable 的使用模式?

发布于 2024-07-18 07:28:26 字数 753 浏览 13 评论 0原文

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

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

问题是我不想对对象进行 GC 处理。 我希望它作为 MessageCollection 的一部分保留下来。

C# 中是否有另一种构造可以为我提供 using 语句的可用性,而无需踩踏 IDisposable 的预期功能。

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?

I am wanting to create an internal messaging system that can tell me the duration of some code being called. I was thinking for ease of use, to make the SystemMessage class implement IDisposable.

I would set a time stamp during the SystemMessage's constructor and if the Dispose was called, I could figure out the duration.

The problem is that I do not want to have the object GC'ed. I want it to stay around as part of a MessageCollection.

Is there another construct in C# that can give me the usability of the Using Statement without stepping on the intended function of IDisposable.

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

香橙ぽ 2024-07-25 07:28:26

问题是我不想对对象进行 GC 处理。 我希望它作为 MessageCollection 的一部分保留下来。

调用 Dispose 不会导致对象被 GC 处理 - 当 GC 进行扫描并且没有任何对象引用它时,就会发生这种情况。 如果您仍然通过 MessageCollection 引用该对象,它将保留下来。

Dispose 可以防止它被 Finalized,但由于您没有使用 Dispose 来清理资源,因此您不会有 Finalizer,也不会关心。

所以,真正唯一的问题是让你的类实现 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 委托。

The problem is that I do not want to have the object GC'ed. I want it to stay around as part of a MessageCollection.

Calling Dispose doesn't cause the object to be GC'ed - that happens when the GC does a sweep and nothing is referencing it. If you're still referencing the object via MessageCollection, it'll stick around.

Dispose can keep it from being Finalized, but since you're not using Dispose to clean up resources, you won't have a Finalizer and you won't care.

So, really the only problem is the confusing semantics around having your calss implement IDisposable even though there's no resources to dispose of.

Personally, I don't see it as a problem. If consumers call Dispose, then fine - they get the time logging. If they don't, then they don't get itme stamps and the worst that happens is they get an FxCop violation.

It is, however, a bit unintuitive - so if this is for public use, I'd suggest offering a more discoverable alternative like:

// 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);
   }
}

which would run and time an Action delegate instead.

只有影子陪我不离不弃 2024-07-25 07:28:26

您是否正在寻找类似于闭包的东西?

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"; } );
        }

Are you looking for something akin to closures?

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

You could fake something... like this...

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"; } );
        }
玩物 2024-07-25 07:28:26

我不认为使用是你想要的。 为什么不让构造函数记录时间,然后在调用 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(); 
}

I don't think using is what you want here. Why not just have the constructor record the time, and then when DownloadAFile is called log the time delta? In your example, if there's an exception, it will log the time of the exception as though the file was downloaded then.

If you actually want the behavior to be like your example, just use a try / finally block, and do the logging in the finally. using is just syntactic sugar for a try / finally block and a call to Dispose.

The equivalent code to your example looks like this:

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(); 
}
笑,眼淚并存 2024-07-25 07:28:26

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

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

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

You can use a System.Diagnostics.Stopwatch inside DownloadAFile() to do the timing every time it's called.

Or, just add the stopwatch code around the call to DownloadAFile() (depending on how you want this to work).

Using IDisposable in this case would not be a good idea.

撩心不撩汉 2024-07-25 07:28:26

我最近一直在关注这个问题,也许 PostSharp 可以帮助你。 它允许您使用一个属性来装饰一个方法,该属性将在您的方法启动和停止时被调用。

http://www.postsharp.org/

我不确定它会做你喜欢的事情,但是它值得研究,而且它有你渴望的“语法糖”!

克里斯

I have been looking at this lately and perhaps PostSharp can help you. It lets you decorate a method with an attribute that will be called when your method starts and when it stops.

http://www.postsharp.org/

I'm not sure it will do what you like, but it's worth investigation and it has that "syntactic sugar" you're craving!

Chris

彻夜缠绵 2024-07-25 07:28:26

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

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

重要的一点是 CreateNewTimedMessage 可能作为一个侧面-effect 创建并存储半永久 Message 对象,但它返回一个临时计时对象(使用 StopWatch 或某种类似机制)它无法在 using 块的范围内生存。 (MessageTimer 可以引用 Message,但反之则不行。)

因此,处置 MessageTimer 对象可能会产生副作用在某处记录最后的时间,但该对象本身将无法生存或复活; 这并不是对 using 结构的滥用,因为您实际上是在处理一个对象。

MessageTimer 的实际实现可能与 Joe 的答案类似。)

Well, this is an oldie, but nobody else seems to have posted the answer which I think is the best style (from what I understand of your requirements):

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

The important point being that CreateNewTimedMessage might as a side-effect create and store a semi-permanent Message object, but it returns a temporary timing object (that uses StopWatch, or some similar mechanism) which doesn't survive the scope of the using block. (It's ok for the MessageTimer to reference the Message, but not the reverse.)

So disposal of the MessageTimer object might have a side-effect of logging the final time somewhere, but the object itself won't survive or be resurrected; this isn't an abuse of the using construct because you really are disposing of an object.

(The actual implementation of the MessageTimer would probably be similar to Joe's answer.)

梦罢 2024-07-25 07:28:26

并不真地。 最接近的是这样的(无论如何,这就是 using() 语句背后发生的事情:)

var message = Collection.CreateNewMessage("FileDownloading")

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

Not really. The closest you can come is something like this (which is what is happening under the hood of the using() statement anyway:)

var message = Collection.CreateNewMessage("FileDownloading")

try
{
    WebClass.DownloadAFile("You Know This File Is Great.XML");
}
finally
{
    message.HowLongHaveIBeenAlive();
}
北笙凉宸 2024-07-25 07:28:26

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;

The using statement is meant to dispose of objects once you are finished with them. The idea of holding on to an object after it has been disposed is not exactly intended. You might try and do something as simple as:

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;
余生一个溪 2024-07-25 07:28:26

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

但我见过这种事情,并且认为如果它保留在应用程序内部就可以了。

问题是我不想
对对象进行 GC 处理。 我想要它
留在附近作为
消息集合。

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

您的消息类可能类似于下面的示例。 调用 Dispose 后,它仍然保持活动状态并且运行良好。 大多数实现 IDisposable 的类在调用 Dispose 后将不可用,因此,如果在调用 Dispose 后访问成员,它们的实现将引发 ObjectDisposeException。 但这绝不是强制性的。

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 ...
    }

    ...
}

It could be argued that this is an abuse of the using construct, so I probably wouldn't implement a public class in a shared class library that uses IDisposable in this way.

But I've seen this kind of thing done and think it is OK if it stays internal to your application.

The problem is that I do not want to
have the object GC'ed. I want it to
stay around as part of a
MessageCollection.

I don't understand this at all. IDisposable has nothing to do with GC, and your message will stay alive if it's referenced as an element of your MessageCollection.

Your message class might look something like the sample below. After Dispose is called, it remains alive and well. Most classes that implement IDisposable are unusable after Dispose is called, and their implementation therefore throws an ObjectDisposedException if members are accessed after calling Dispose. But this is by no means mandatory.

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 ...
    }

    ...
}
智商已欠费 2024-07-25 07:28:26

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

出于您的目的,特别是因为您想回收该对象,我会考虑编写您自己的接口。 当你调用一个方法来开始计时时,只需获取 datetime.now 的值即可; 当你停止计时器时再次。 然后用停止时间减去开始时间,就得到了持续时间。

避免在每个类实例上使用真正的 Timer 对象。 计时器使用线程池线程,这意味着每条消息将至少消耗一个线程——如果系统中的消息太多,您的应用程序将因线程切换而减慢速度。 另外,如果计时器没有正确处理,它们可能不会释放其 ThreadPool 线程,并且本质上会出现线程泄漏。

The 'using' statement actually compiles down to a Try/Finally that expects the object to implement IDisposable--"using" is just a shortcut provided by the language.

For your purposes, especially since you want to recycle the object, I would consider writing your own interface. When you call a method to start timing just grab the value of datetime.now; and again when you stop the timer. Then subtract starttime from stoptime and you'll have the duration.

Avoid using a true Timer object on each class instance. Timer's use a ThreadPool thread, which means that each message will consume at least one thread--if too many messages are in the system your application will slow down as a result of the thread switching. Also, if the timer's aren't properly disposed they may not release their ThreadPool threads and you will essentially have a thread leak.

温柔少女心 2024-07-25 07:28:26

最初 IDisposable 旨在作为在 C# 中引入确定性清理的一种方式,但我已经看到 作者实现使用using/Dispose 功能用于与您正在谈论的内容相关的内容。

就我个人而言,我对此不太高兴,因为它破坏了 IDisposable 背后的概念,但由于没有替代方案,所以这取决于您想要成为多么正统的问题。 我当然可以理解您为什么要这样做,但这样做会使解释 IDisposable 接口的目的和重要性变得更加困难。

Originally IDisposable was intended as a way to introduce deterministic clean-up in C#, but I have seen authors and implementations use the using/Dispose feature for something along the lines of what you are talking about.

Personally, I'm not too happy about it as it undermines the concept behind IDisposable, but since there's isn't an alternative it comes down to a matter of how orthodox you want to be. I can certainly see why you want to do it, but by doing so you make it harder to explain the purpose and the importance of the IDisposable interface.

潦草背影 2024-07-25 07:28:26

再次查看您的问题后,我不明白有问题的对象实际上是一条消息,除非它更像是一条跟踪消息(用于调试帮助)。

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

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

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"
    }
}

After reviewing your question again I don't see how the object in question is really a Message, unless it is more like a tracing message (for debugging assistance).

If you're looking for something more along those lines then here is a very rough approach using delegates. Essentially you create a delegate for each method you want to call and time, then pass the delegate and the method arguments off to a helper who is responsible for the actual calling of the method and timing its duration.

The obvious shortcoming in my example is that I sacrificed type-safe arguments since I wasn't sure if it was exactly what you're looking for or not. The other issue is that you would need to add a new delegate every time you want to call a method which has a method signature that you don't already have a delegate for:

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"
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文