Как я могу создать класс для получения делегата с неизвестным количеством параметров?

StackOverflow https://stackoverflow.com/questions/1601510

  •  05-07-2019
  •  | 
  •  

Вопрос

Мне постоянно приходится писать синхронизированные службы 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();
    }
}
Это было полезно?

Решение

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

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

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

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

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

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

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

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

Другие советы

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

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

ThreadStart taskToPerform = delegate() { // do your stuff here
   YourService.Call(X, Y, Z);
};
Template.AddProcess(taskToPerform);

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

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

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

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

Если параметр или свойство вашего метода для делегата относится только к типу 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 с любыми другими дополнительными данными, которые вы хотели бы передать.

Вы ищете:- Params Object [] ParametRovalues

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top