不明な数のパラメーターを持つデリゲートを受け取るクラスを設計するにはどうすればよいですか?
質問
キューの外部でポーリングしたり、時間指定されたプロセスを実行したりする時間指定のWindowsサービスを作成しなければならないことを常に感じています。そのため、これを行うためのかなり堅牢なテンプレートがありますが、これを行うサービスを作成するたびに、同じテンプレートから始めて、その中にプロセスを記述します。
今朝、このテンプレートを実際にプロセスを注入できるフレームワークに変えることができるかどうか疑問に思っています。私の基本的なテンプレートは、122行のコードです。各プロセスの要件が異なるため-つまり、引数の数、引数の種類、依存関係が異なる(Webサービスに依存するもの、データベースに依存するものなど)ために、基本テンプレートを設定する方法がわかりません。挿入されたプロセス。
テンプレートの中心は、プロセスを初期化および開始するときに停止し、プロセスが完了するとタイマーを再起動するタイマーです。次に、プロセスメソッドと依存関係をテンプレートに追加します。
これを行う方法について何かアイデアがありますか?私は依存性注入を見てきましたが、多くの場合、データストア接続などの注入に既に使用していますか?不明な数/タイプのパラメーターを持つデリゲートをクラスに注入する方法はありますか?私はこれを間違って見ていますか?
これは私が持っているテンプレートです:
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を生成し、実行する必要のあるプロセスを挿入して、WindowsサービスからTimedProcessコードを完全に削除し、DLLを参照するように変更しようとしています。
編集:皆様のご協力に感謝します。 RundProcess()メソッドをTimedProcessライブラリの外にプッシュして、コンストラクターで that をActionとして渡すと、探しているすべての方法が単純化されることに気付きました:
[簡潔にするために簡略化]
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();
}
}
解決
ここでのアプローチの1つは、すべてのデリゲートがAction
または多分Func<T>
になるようにキャプチャされた変数を使用し、匿名メソッド、キャプチャされた変数の魔法によって残りを呼び出し元に任せることです。など-すなわち
DoStuff( () => DoSomethingInteresting("abc", 123) );
(注意:非同期/キャプチャに注意してください-しばしば悪い組み合わせ)
where DoStuff
はExpression
を受け入れます。その後、<=>を呼び出すと、パラメータが自動的に追加されます。一部のRPCライブラリコードでは、<=>を使用してこのアプローチを逆に取っています。したがって、サービスインターフェイス(通常)を表現し、のような:
Invoke(Expression<Action<TService>> action) {...}
Invoke<TValue>(Expression<Func<TService,TValue>> func) {...}
呼ばれる、たとえば:
proxy.Invoke(svc => svc.CancelOrder(orderNumber));
その後、呼び出し元は、そのインターフェイスの実装があれば 何をすべきかを言います。代わりに、<=>を引き離し、呼び出されているメソッドや引数などを見て、これらをRPCレイヤーに渡します。興味がある場合は、こちら、またはコードについて詳しく説明しています。 こちら 。
他のヒント
不明なパラメーターセットを受け取るデリゲートを使用する1つの方法は、オブジェクトの配列を渡すことです。その後、配列の長さをパラメーターの数として使用できます。また、どの型もオブジェクトに変換できるため、何でも渡すことができます。
パラメータなしでデリゲートを使用して、<!> quot; service call <!> quot;:
を表すことができます。ThreadStart taskToPerform = delegate() { // do your stuff here
YourService.Call(X, Y, Z);
};
Template.AddProcess(taskToPerform);
完全を期すために、実装例をいくつか示します。
両方とも、すでに説明したラップされたデリゲート方法論を使用します。 <!> quot; params <!> quot;を使用します。そして、ジェネリックを使用します。どちらも<!> quot; async / capture <!> quot;を避けます。問題。実際、これはイベントの実装方法に非常に似ています。
事前に申し訳ありませんが、すべて1つの長いコードブロックに含まれています。私はそれを3つのサブ名前空間に分割しました:
- 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 [] parameterValues
http://msdn.microsoft.com /en-us/library/w5zay9db%28VS.71%29.aspx