如何设计一个类来接收具有未知数量参数的委托?

时间:2009-10-21 15:12:14

标签: c# .net c#-3.0

我不断发现自己必须编写定时窗口服务来轮询外部队列或运行定时进程。因此,我有一个相当强大的模板来执行此操作,但我发现每次编写服务来执行此操作时,我都会使用相同的模板,然后在其中编写流程。

今天早上我发现自己想知道我是否真的可以将这个模板变成一个我可以注入流程的框架。我的基本模板只有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。

修改:感谢大家的帮助。我意识到如果我将我的RunProcess()方法推送到我的TimedProcess库之外并将那个作为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();
    }
}

6 个答案:

答案 0 :(得分:3)

这里的一种方法是使用捕获的变量,以便所有代理基本上成为ActionFunc<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拉开并查看被调用的方法,args等 - 并将它们传递给RPC层。如果您有兴趣,可以进一步讨论here,或者代码可用here

答案 1 :(得分:1)

使用一组未知参数的委托的一种方法是传递一个对象数组。然后,您可以使用数组的长度作为参数的数量,并且由于任何类型都可以转换为对象,因此您可以传递任何内容。

答案 2 :(得分:1)

您可以使用不带参数的委托来表示“服务电话”:

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

答案 3 :(得分:1)

为了完整性,这里有一些示例实现。

两者都使用已经讨论过的包装代理方法。一个使用“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);
            }
        }
    }
}

答案 4 :(得分:0)

如果委托的方法参数或属性只是Delegate类型,则可以使用它的DynamicInvoke方法来调用委托,无论它的签名是什么。像这样:

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

你真的应该能够使用一个强类型的委托,可能是一个Func或Action,可以接受你需要传递给proccess的任何参数。

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和你想传递的任何其他额外数据。

答案 5 :(得分:0)

您在寻找: -  params object [] parameterValues

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