Как я могу создать класс для получения делегата с неизвестным количеством параметров?
Вопрос
Мне постоянно приходится писать синхронизированные службы 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