Существует ли шаблон использования, который не зависит от IDisposable? - PullRequest
3 голосов
/ 01 мая 2009

Я хочу создать внутреннюю систему обмена сообщениями, которая может сообщать мне продолжительность вызова некоторого кода. Я думал о простоте использования, чтобы класс SystemMessage реализовал IDisposable.

Я бы установил метку времени во время конструктора SystemMessage, и если бы был вызван Dispose, я мог бы определить продолжительность.

Проблема в том, что я не хочу иметь объект GC'ed. Я хочу, чтобы он оставался как часть MessageCollection.

Есть ли в C # другая конструкция, которая может дать мне удобство использования оператора Statement, не наступая на предполагаемую функцию 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?

Ответы [ 12 ]

5 голосов
/ 01 мая 2009

Проблема в том, что я не хочу иметь объект GC'ed. Я хочу, чтобы он оставался как часть MessageCollection.

Вызов Dispose не приводит к тому, что объект GC'ed - это происходит, когда GC выполняет развертку, и ничто не ссылается на него. Если вы все еще ссылаетесь на объект с помощью MessageCollection, он останется на месте.

Утилизация может помешать его завершению, но поскольку вы не используете Утилиту для очистки ресурсов, у вас не будет финализатора и вам все равно.

Так что, действительно, единственная проблема заключается в сбивающей с толку семантике, связанной с тем, что ваш 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.

2 голосов
/ 01 мая 2009

Вы ищете что-то похожее на замыкания?

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"; } );
        }
1 голос
/ 06 апреля 2011

Ну, это старенький, но никто другой, кажется, не опубликовал ответ, который я считаю лучшим стилем (насколько я понимаю из ваших требований):

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

Важным моментом является то, что CreateNewTimedMessage может в качестве побочного эффекта создать и сохранить полупостоянный объект Message, но он возвращает временный объект синхронизации (который использует StopWatch, или некоторый аналогичный механизм), который не выходит за рамки блока using. (Это нормально для MessageTimer ссылаться на Message, но не наоборот.)

Таким образом, удаление объекта MessageTimer может иметь побочный эффект записи последнего времени где-либо, но сам объект не выживет и не воскреснет; это не злоупотребление конструкцией using, потому что вы действительно избавляетесь от объекта.

(Реальная реализация MessageTimer, вероятно, будет аналогична ответу Джо.)

1 голос
/ 02 мая 2009

Я смотрю на это в последнее время и, возможно, PostSharp может вам помочь. Он позволяет вам украсить метод атрибутом, который будет вызываться при запуске и при остановке вашего метода.

http://www.postsharp.org/

Я не уверен, что он будет делать то, что вам нравится, но это стоит исследовать, и в нем есть тот "синтаксический сахар", которого вы жаждете!

Chris

1 голос
/ 01 мая 2009

Вы можете использовать System.Diagnostics.Stopwatch внутри DownloadAFile () для определения времени каждого вызова.

Или просто добавьте код секундомера к вызову DownloadAFile () (в зависимости от того, как вы хотите, чтобы это работало).

Использование IDisposable в этом случае не будет хорошей идеей.

1 голос
/ 01 мая 2009

Я не думаю, что использование - это то, что вы хотите здесь. Почему бы просто не сделать так, чтобы конструктор записывал время, а затем, когда 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(); 
}
0 голосов
/ 01 мая 2009

После повторного рассмотрения вашего вопроса я не вижу, что рассматриваемый объект действительно является Сообщением, если только оно больше не похоже на сообщение трассировки (для помощи при отладке).

Если вы ищете что-то еще в этом направлении, то вот очень грубый подход с использованием делегатов. По сути, вы создаете делегат для каждого метода и времени, которое вы хотите вызвать, а затем передаете делегат и аргументы метода помощнику, который отвечает за фактический вызов метода и синхронизацию его продолжительности.

Очевидным недостатком в моем примере является то, что я пожертвовал безопасными типами аргументами, поскольку не был уверен, что это именно то, что вы ищете, или нет. Другая проблема заключается в том, что вам нужно будет добавлять новый делегат каждый раз, когда вы хотите вызвать метод с сигнатурой метода, для которого у вас еще нет делегата:

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"
    }
}
0 голосов
/ 01 мая 2009

Оператор 'using' фактически компилируется в Try / Наконец, который ожидает, что объект реализует IDisposable - «using» - это просто ярлык, предоставленный языком.

Для ваших целей, особенно если вы хотите переработать объект, я бы подумал о написании вашего собственного интерфейса. Когда вы вызываете метод для начала синхронизации, просто возьмите значение datetime.now; и снова, когда вы остановите таймер. Затем вычтите время начала из времени остановки, и вы получите продолжительность.

Избегайте использования истинного объекта Timer в каждом экземпляре класса. Таймер использует поток ThreadPool, что означает, что каждое сообщение будет использовать хотя бы один поток - если в системе слишком много сообщений, ваше приложение будет замедляться в результате переключения потока. Кроме того, если таймеры не расположены должным образом, они могут не освободить свои потоки ThreadPool, и вы по существу будете иметь утечку потока.

0 голосов
/ 01 мая 2009

Можно утверждать, что это злоупотребление конструкцией using, поэтому я, вероятно, не стал бы реализовывать открытый класс в библиотеке общих классов, которая использует IDisposable таким образом.

Но я видел, как это делается, и думаю, что все в порядке, если оно остается внутренним для вашего приложения.

Проблема в том, что я не хочу иметь объект GC'ed. Я хочу это остаться как часть MessageCollection.

Я совсем не понимаю этого. IDisposable не имеет ничего общего с GC, и ваше сообщение останется в живых, если оно будет упоминаться как элемент вашей коллекции MessageCollection.

Ваш класс сообщений может выглядеть примерно так, как показано ниже. После вызова Dispose он остается живым и здоровым. Большинство классов, которые реализуют IDisposable, становятся непригодными после вызова Dispose, и поэтому их реализация генерирует исключение ObjectDisposedException, если к элементам обращаются после вызова Dispose. Но это ни в коем случае не обязательно.

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

    ...
}
0 голосов
/ 01 мая 2009

Изначально IDisposable был задуман как способ введения детерминированной очистки в C #, но я видел, как авторов и реализации используют функцию using / Dispose за то, о чем вы говорите.

Лично я не слишком доволен этим, поскольку это подрывает концепцию, стоящую за IDisposable, но поскольку альтернативы нет, все сводится к вопросу о том, насколько ортодоксальными вы хотите быть. Я, конечно, могу понять, почему вы хотите это сделать, но тем самым вы усложняете объяснение цели и важности интерфейса IDisposable.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...