Как я могу разработать класс для получения делегата с неизвестным количеством параметров? - PullRequest
4 голосов
/ 21 октября 2009

Мне постоянно приходится писать синхронизированные службы Windows, которые опрашивают вне очередей или запускают синхронизированные процессы. Следовательно, у меня есть достаточно надежный шаблон для этого, но я нахожу, что каждый раз, когда я пишу сервис для этого, я начинаю с одного и того же шаблона, а затем пишу процесс внутри него.

Этим утром я задаюсь вопросом, могу ли я на самом деле превратить этот шаблон в структуру, в которую я мог бы внедрить свой процесс. Мой базовый шаблон - всего 122 строки кода. Из-за различий в требованиях к каждому процессу - то есть разного количества аргументов, разных типов аргументов и разных зависимостей (некоторые зависят от веб-служб, некоторые - от баз данных и т. Д.), Я не могу понять, как настроить базовый шаблон для получения Внедренный процесс.

Сердцем шаблона является просто таймер, который останавливается при инициализации и запуске процесса, а затем перезапускает таймер после завершения процесса. Затем я добавляю свой метод процесса и любые зависимости прямо в шаблон.

У кого-нибудь есть идеи, как это сделать? Я смотрел на внедрение зависимостей и часто уже использовал его для внедрения таких вещей, как подключение к хранилищу данных? Есть ли способ ввести делегата с неизвестным числом / типом параметров в класс? Я смотрю на это неправильно?

Это шаблон, который у меня есть:

TimedProcess.Template.cs

using System;
using System.Timers;

public partial class TimedProcess : IDisposable
{
    private Timer timer;

    public bool InProcess { get; protected set; }

    public bool Running
    {
        get
        {
            if (timer == null)
                return false;

            return timer.Enabled;
        }
    }

    private void InitTimer(int interval)
    {
        if (timer == null)
        {
            timer = new Timer();
            timer.Elapsed += TimerElapsed;
        }
        Interval = interval;
    }

    public void InitExecuteProcess()
    {
        timer.Stop();
        InProcess = true;
        RunProcess();
        InProcess = false;
        timer.Start();   
    }

    public void TimerElapsed(object sender, ElapsedEventArgs e)
    {
        InitExecuteProcess();
    }

    public void Start()
    {
        if (timer != null && timer.Interval > 0)
            timer.Start();
    }

    public void Start(int interval)
    {
        InitTimer(interval);
        Start();
    }

    public void Stop()
    {
        timer.Stop();
    }

    public TimedProcess()
        : this(0)
    {
    }

    public TimedProcess(int interval)
    {
        if (interval > 0)
            InitTimer(interval);
    }

    private disposed = false;
    public Dispose(bool disposing)
    {
        if (disposed || !disposing)
            return;

        timer.Dispose();

        disposed = true;
    }

    public Dispose()
    {
        Dispose(true);
    }

    ~TimedProcess()
    {
        Dispose(false);
    }
}

TimedProcess.cs

using System;

public partial class TimedProcess
{
    public void RunProcess()
    {
        //Hook process to run in here
    }
}

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

Редактировать : Спасибо всем за помощь. Я понял, что если я вытолкну свой метод RunProcess () за пределы моей библиотеки TimedProcess и передам , что , в качестве действия в конструкторе, то это упростит все, что я искал:

[Упрощено для краткости]

public class TimedProcess
{
    Action RunProcess;
    Timer timer = new Timer();

    private void TimerElapsed(object sender, ElapsedEventArgs e)
    {
        if (RunProcess != null)
            RunProcess();
    }

    public TimedProcess(Action action, int interval)
    {
        timer.Interval = interval;
        RunProcess = action;
        timer.Start();
    }
}

Ответы [ 6 ]

3 голосов
/ 21 октября 2009

Один из подходов здесь состоит в том, чтобы использовать захваченные переменные так, чтобы все делегаты по существу становились Action или, возможно, Func<T> - и оставляли остальное вызывающей стороне с помощью магии анонимных методов, захваченных переменных, и т. д. - т.е.

DoStuff( () => DoSomethingInteresting("abc", 123) );

(предостережение: следите за асинхронностью / захватом - часто это плохая комбинация)

, где DoStuff принимает Action. Затем, когда вы вызываете Action, параметры добавляются автоматически и т. Д. В некотором коде библиотеки RPC я использовал этот подход другим способом, используя Expression - поэтому я выражаю интерфейс службы (как обычно) и затем получаю методы как:

Invoke(Expression<Action<TService>> action) {...}
Invoke<TValue>(Expression<Func<TService,TValue>> func) {...}

называется, например:

proxy.Invoke(svc => svc.CancelOrder(orderNumber));

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

1 голос
/ 21 октября 2009

Для полноты вот несколько примеров реализации.

Оба используют методологию упакованного делегата, уже обсужденную. Один использует «params», а другой - дженерики. Оба избегают проблемы «асинхронности / захвата». На самом деле, это очень похоже на то, как реализуются события.

Извините заранее, все это в одном длинном кодовом блоке. Я разделил его на три подпространства:

  • FakeDomain (например, содержит макеты)
  • UsingParams (содержит реализацию, в которой используется ключевое слово params)
  • UsingGenerics (содержит реализацию, которая использует дженерики)

См. Ниже:

using System;
using System.Timers;
using StackOverflow.Answers.InjectTaskWithVaryingParameters.FakeDomain;

namespace StackOverflow.Answers.InjectTaskWithVaryingParameters
{
    public static class ExampleUsage
    {
        public static void Example1()
        {
            // using timed task runner with no parameters

            var timedProcess = new UsingParams.TimedProcess(300, FakeWork.NoParameters);

            var timedProcess2 = new UsingGenerics.TimedProcess(300, FakeWork.NoParameters);
        }

        public static void Example2()
        {
            // using timed task runner with a single typed parameter 

            var timedProcess =
                new UsingParams.TimedProcess(300,
                    p => FakeWork.SingleParameter((string)p[0]),
                    "test"
                );

            var timedProcess2 =
                new UsingGenerics.TimedProcess<StringParameter>(
                        300,
                        p => FakeWork.SingleParameter(p.Name),
                        new StringParameter()
                        {
                            Name = "test"
                        }
                );
        }

        public static void Example3()
        {
            // using timed task runner with a bunch of variously typed parameters 

            var timedProcess =
                new UsingParams.TimedProcess(300,
                    p => FakeWork.LotsOfParameters(
                        (string)p[0],
                        (DateTime)p[1],
                        (int)p[2]),
                    "test",
                    DateTime.Now,
                    123
                );

            var timedProcess2 =
                new UsingGenerics.TimedProcess<LotsOfParameters>(
                    300,
                    p => FakeWork.LotsOfParameters(
                        p.Name,
                        p.Date,
                        p.Count),
                    new LotsOfParameters()
                    {
                        Name = "test",
                        Date = DateTime.Now,
                        Count = 123
                    }
                );
        }
    }

    /* 
     * Some mock objects for example.
     * 
     */
    namespace FakeDomain
    {
        public static class FakeWork
        {
            public static void NoParameters()
            {
            }
            public static void SingleParameter(string name)
            {
            }
            public static void LotsOfParameters(string name, DateTime Date, int count)
            {
            }
        }

        public class StringParameter
        {
            public string Name { get; set; }
        }

        public class LotsOfParameters
        {
            public string Name { get; set; }
            public DateTime Date { get; set; }
            public int Count { get; set; }
        }
    }

    /*
     * Advantages: 
     *      - no additional types required         
     * Disadvantages
     *      - not strongly typed
     *      - requires explicit casting
     *      - requires "positional" array references 
     *      - no compile time checking for type safety/correct indexing position
     *      - harder to maintin if parameters change
     */
    namespace UsingParams
    {
        public delegate void NoParametersWrapperDelegate();
        public delegate void ParamsWrapperDelegate(params object[] parameters);

        public class TimedProcess : IDisposable
        {
            public TimedProcess()
                : this(0)
            {
            }

            public TimedProcess(int interval)
            {
                if (interval > 0)
                    InitTimer(interval);
            }

            public TimedProcess(int interval, NoParametersWrapperDelegate task)
                : this(interval, p => task(), null) { }

            public TimedProcess(int interval, ParamsWrapperDelegate task, params object[] parameters)
                : this(interval)
            {
                _task = task;
                _parameters = parameters;
            }

            private Timer timer;
            private ParamsWrapperDelegate _task;
            private object[] _parameters;

            public bool InProcess { get; protected set; }

            public bool Running
            {
                get
                {
                    return timer.Enabled;
                }
            }

            private void InitTimer(int interval)
            {
                if (timer == null)
                {
                    timer = new Timer();
                    timer.Elapsed += TimerElapsed;
                }
                timer.Interval = interval;
            }

            public void InitExecuteProcess()
            {
                timer.Stop();
                InProcess = true;
                RunTask();
                InProcess = false;
                timer.Start();
            }

            public void RunTask()
            {
                TimedProcessRunner.RunTask(_task, _parameters);
            }

            public void TimerElapsed(object sender, ElapsedEventArgs e)
            {
                InitExecuteProcess();
            }

            public void Start()
            {
                if (timer != null && timer.Interval > 0)
                    timer.Start();
            }

            public void Start(int interval)
            {
                InitTimer(interval);
                Start();
            }

            public void Stop()
            {
                timer.Stop();
            }

            private bool disposed = false;

            public void Dispose(bool disposing)
            {
                if (disposed || !disposing)
                    return;

                timer.Dispose();

                disposed = true;
            }

            public void Dispose()
            {
                Dispose(true);
            }

            ~TimedProcess()
            {
                Dispose(false);
            }
        }

        public static class TimedProcessRunner
        {
            public static void RunTask(ParamsWrapperDelegate task)
            {
                RunTask(task, null);
            }

            public static void RunTask(ParamsWrapperDelegate task, params object[] parameters)
            {
                task.Invoke(parameters);
            }
        }
    }

    /*
     * Advantage of this method: 
     *      - everything is strongly typed         
     *      - compile time and "IDE time" verified
     * Disadvantages:
     *      - requires more custom types 
     */
    namespace UsingGenerics
    {
        public class TimedProcess : TimedProcess<object>
        {
            public TimedProcess()
                : base() { }
            public TimedProcess(int interval)
                : base(interval) { }
            public TimedProcess(int interval, NoParametersWrapperDelegate task)
                : base(interval, task) { }
        }

        public class TimedProcess<TParam>
        {
            public TimedProcess()
                : this(0)
            {
            }

            public TimedProcess(int interval)
            {
                if (interval > 0)
                    InitTimer(interval);
            }
            public TimedProcess(int interval, NoParametersWrapperDelegate task)
                : this(interval, p => task(), default(TParam)) { }

            public TimedProcess(int interval, WrapperDelegate<TParam> task, TParam parameters)
                : this(interval)
            {
                _task = task;
                _parameters = parameters;
            }

            private Timer timer;
            private WrapperDelegate<TParam> _task;
            private TParam _parameters;

            public bool InProcess { get; protected set; }

            public bool Running
            {
                get
                {
                    return timer.Enabled;
                }
            }

            private void InitTimer(int interval)
            {
                if (timer == null)
                {
                    timer = new Timer();
                    timer.Elapsed += TimerElapsed;
                }
                timer.Interval = interval;
            }

            public void InitExecuteProcess()
            {
                timer.Stop();
                InProcess = true;
                RunTask();
                InProcess = false;
                timer.Start();
            }

            public void RunTask()
            {
                TaskRunner.RunTask(_task, _parameters);
            }

            public void TimerElapsed(object sender, ElapsedEventArgs e)
            {
                InitExecuteProcess();
            }

            public void Start()
            {
                if (timer != null && timer.Interval > 0)
                    timer.Start();
            }

            public void Start(int interval)
            {
                InitTimer(interval);
                Start();
            }

            public void Stop()
            {
                timer.Stop();
            }

            private bool disposed = false;

            public void Dispose(bool disposing)
            {
                if (disposed || !disposing)
                    return;

                timer.Dispose();

                disposed = true;
            }

            public void Dispose()
            {
                Dispose(true);
            }

            ~TimedProcess()
            {
                Dispose(false);
            }
        }

        public delegate void NoParametersWrapperDelegate();
        public delegate void WrapperDelegate<TParam>(TParam parameters);

        public static class TaskRunner
        {
            public static void RunTask<TParam>(WrapperDelegate<TParam> task)
            {
                RunTask(task, default(TParam));
            }

            public static void RunTask<TParam>(WrapperDelegate<TParam> task, TParam parameters)
            {
                task.Invoke(parameters);
            }
        }
    }
}
1 голос
/ 21 октября 2009

Вы можете использовать делегата без параметров для представления «вызова службы»:

ThreadStart taskToPerform = delegate() { // do your stuff here
   YourService.Call(X, Y, Z);
};
Template.AddProcess(taskToPerform);
1 голос
/ 21 октября 2009

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

0 голосов
/ 21 октября 2009

Вы ищете: - params object [] parameterValues ​​

http://msdn.microsoft.com/en-us/library/w5zay9db%28VS.71%29.aspx

0 голосов
/ 21 октября 2009

Если параметр или свойство вашего метода для делегата просто имеет тип Delegate, вы можете использовать его метод DynamicInvoke для вызова делегата, независимо от того, какая это подпись. Вот так:

public void CallDelegate(Delegate del) {
  result = del.DynamicInvoke(1, 2, 3, 'A', 'B', 'C');
}

Вы действительно должны иметь возможность использовать строго типизированный делегат, возможно, Func или Action, который может принимать любые параметры, которые вам нужно передать процессу.

public void CallDelegate(Func<int, int, char, char> del) {
  result = del(1, 2, 'A', 'B');
}

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

//The interface
interface ITimerProcess {
  TimeSpan Period {get;}
  void PerformAction(string someInfo);
}

//A process
class SayHelloProcess : ITimerProcess {

  public TimeSpan Period { get { return TimeSpan.FromHours(1); } }

  public void PerformAction(string someInfo) {
    Console.WriteLine("Hello, {0}!", someInfo);
  }
}

Для краткости на этом я закончу, ваша служба может обнаружить весь процесс, найдя классы, которые реализуют ITimerProcess, а затем создаст таймер для каждого на основе свойства Period, которое предоставляет каждый из них. Таймер просто должен вызвать PerformAction с любыми другими дополнительными данными, которые вы хотели бы передать.

...