How can I design a class to receive a delegate having an unknown number of parameters?

前端 未结 6 1133
耶瑟儿~
耶瑟儿~ 2020-12-10 19:55

I continuously find myself having to write timed windows services that poll outside queues or run timed processes. I consequently have a fairly robust template by which to

相关标签:
6条回答
  • 2020-12-10 20:02

    If your method parameter or property for the delegate is just of type Delegate you can use it's DynamicInvoke method to call the delegate, no matter what it's signature is. Like so:

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

    You really should be able to use a strong typed delegate, probably an Func or Action that can take whatever parameters you need to pass to the proccess.

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

    Personally, I would create an interface that all of the processes had to implement, then have the service discover all of the objects that implement it. That way they could provide their timing needs and a strongly typed method to call to the service. Something like this:

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

    For brievity I'll end it there, your service could descover all the process by looking for classes that implement ITimerProcess, then creating a timer for each based on the Period property each exposes. The timer would just have to call PerformAction with any other extra data you would like to pass.

    0 讨论(0)
  • 2020-12-10 20:11

    One approach here would be to use captured variables so that all delegates essentially become Action or maybe Func<T> - and leave the rest to the caller via the magic of anonymous methods, captured variables, etc - i.e.

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

    (caveat: watch out for async / capture - often a bad combination)

    where DoStuff accepts an Action. Then when you invoke the Action the parameters are automatically added etc. In some RPC library code I've taken this approach the other way, using Expression - so I express a service interface (as normal), and then have methods like:

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

    called, for example:

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

    Then the caller says what to if we had an implementation of that interface - except we never actually do; instead, we pull the Expression apart and look at the method being called, the args, etc - and pass these to the RPC layer. If you are interested, it is discussed more here, or the code is available here.

    0 讨论(0)
  • 2020-12-10 20:14

    For completeness, here's some example implementations.

    Both use the wrapped delegate methodology already discussed. One uses "params" and one uses generics. Both avoid the "async / capture" problem. In fact, this is very similar to how events are implemented.

    Sorry in advance, it's all in one long code block. I've divided it up into three subnamespaces:

    • FakeDomain (holds mocks for example)
    • UsingParams (holds implementation that uses params keyword)
    • UsingGenerics (holds implementation that uses generics)

    See below:

    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);
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-10 20:17

    You can use a delegate without parameters to represent the "service call":

    ThreadStart taskToPerform = delegate() { // do your stuff here
       YourService.Call(X, Y, Z);
    };
    Template.AddProcess(taskToPerform);
    
    0 讨论(0)
  • 2020-12-10 20:24

    Are you looking for:- params object[] parameterValues

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

    0 讨论(0)
  • 2020-12-10 20:25

    One means of having a delegate that takes an unknown set of parameters is to pass an array of objects. You can then use the array's length as the number of parameters, and since any type is convertable into an object, you can pass anything.

    0 讨论(0)
提交回复
热议问题