¿Cómo puedo diseñar una clase para recibir un delegado que tiene un número desconocido de parámetros?

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

  •  05-07-2019
  •  | 
  •  

Pregunta

Continuamente tengo que escribir servicios de Windows cronometrados que sondean fuera de las colas o ejecutan procesos cronometrados.En consecuencia, tengo una plantilla bastante sólida para hacer esto, pero encuentro que cada vez que escribo un servicio para hacer esto, comienzo con la misma plantilla y luego escribo el proceso dentro de ella.

Esta mañana me pregunto si realmente podría convertir esta plantilla en un marco en el que podría inyectar mi proceso.Mi plantilla básica tiene solo 122 líneas de código.Debido a los diferentes requisitos para cada proceso, es decirdiferente número de argumentos, diferentes tipos de argumentos y diferentes dependencias (algunos dependen de servicios web, otros de bases de datos, etc.) No puedo entender cómo configurar mi plantilla básica para recibir un proceso inyectado.

El corazón de la plantilla es simplemente un temporizador que se detiene cuando se inicializa e inicia el proceso y luego reinicia el temporizador una vez que se completa el proceso.Luego agrego mi método de proceso y cualquier dependencia directamente en la plantilla.

¿Alguien tiene alguna idea de cómo hacer esto?He analizado la inyección de dependencia y, a menudo, ya la uso para inyectar cosas como la conectividad del almacén de datos.¿Hay alguna manera de inyectar un delegado con un número/tipo desconocido de parámetros en una clase?¿Estoy viendo esto incorrectamente?

Esta es la plantilla que tengo:

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

Proceso cronometrado.cs

using System;

public partial class TimedProcess
{
    public void RunProcess()
    {
        //Hook process to run in here
    }
}

Así que estoy buscando modificarlo para que mi Servicio de Windows genere un nuevo TimedProcess y lo inyecte con el proceso que necesita ejecutarse, eliminando así el código TimedProcess de mi Servicio de Windows por completo y haciendo que haga referencia a la DLL.

Editar:Gracias por la ayuda de todos.Me di cuenta de que si empujo mi método RunProcess() fuera de mi biblioteca TimedProcess y paso eso como una Acción en el constructor, entonces esto simplifica todo de la manera que estaba buscando:

[Simplificado por brevedad]

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();
    }
}
¿Fue útil?

Solución

Un enfoque aquí sería utilizar variables capturadas para que todos los delegados esencialmente convertirse Action o tal vez Func<T> - y dejar el resto a la persona que llama a través de la magia de métodos anónimos, variables capturadas, etc., es decir.

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

(advertencia:cuidado con async/captura - a menudo una mala combinación)

dónde DoStuff acepta un Action.Entonces cuando invocas el Action los parámetros se agregan automáticamente, etc.En algún código de biblioteca RPC, he tomado este enfoque al revés, usando Expression - Entonces expreso una interfaz de servicio (como es normal) y luego tengo métodos como:

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

llamado, por ejemplo:

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

Entonces la persona que llama dice qué si teníamos una implementación de esa interfaz, excepto que en realidad nunca la tenemos;en lugar de eso, tiramos del Expression sepárelos y observe el método que se llama, los argumentos, etc., y páselos a la capa RPC.Si estás interesado, se comenta más. aquí, o el código está disponible aquí.

Otros consejos

Una forma de tener un delegado que toma un conjunto desconocido de parámetros es pasar una matriz de objetos. Luego puede usar la longitud de la matriz como el número de parámetros, y dado que cualquier tipo es convertible en un objeto, puede pasar cualquier cosa.

Puede usar un delegado sin parámetros para representar la " llamada de servicio " ;:

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

Para completar, aquí hay algunos ejemplos de implementaciones.

Ambos usan la metodología de delegado envuelto ya discutido. Uno usa & Quot; params & Quot; y uno usa genéricos. Ambos evitan la & "; Async / capture &"; problema. De hecho, esto es muy similar a cómo se implementan los eventos.

Lo siento de antemano, todo está en un bloque de código largo. Lo he dividido en tres espacios de subnombres:

  • FakeDomain (contiene simulacros, por ejemplo)
  • UsingParams (contiene la implementación que usa la palabra clave params)
  • UsingGenerics (contiene una implementación que usa genéricos)

Ver abajo:

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

Si el parámetro o propiedad de su método para el delegado es solo del tipo Delegado, puede usar su método DynamicInvoke para llamar al delegado, sin importar cuál sea su firma. Me gusta así:

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

Realmente debería poder utilizar un delegado de tipo fuerte, probablemente un Func o Acción que pueda tomar los parámetros que necesite para pasar al proceso.

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

Personalmente, crearía una interfaz que todos los procesos debían implementar, y luego haría que el servicio descubriera todos los objetos que la implementan. De esa forma, podrían proporcionar sus necesidades de tiempo y un método fuertemente tipado para llamar al servicio. Algo como esto:

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

Por brevedad lo terminaré allí, su servicio podría descubrir todo el proceso buscando clases que implementen ITimerProcess, luego creando un temporizador para cada uno basado en la propiedad Period que cada uno expone. El temporizador solo tendría que llamar a PerformAction con cualquier otro dato adicional que le gustaría pasar.

¿Está buscando?-- params objeto [] parametervalues

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top