Come posso progettare una classe per ricevere un delegato con un numero sconosciuto di parametri?

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

  •  05-07-2019
  •  | 
  •  

Domanda

Mi ritrovo continuamente a dover scrivere servizi Windows temporizzati che eseguono il polling fuori dalle code o eseguono processi temporizzati.Di conseguenza dispongo di un modello abbastanza robusto con cui farlo, ma trovo che ogni volta che scrivo un servizio per farlo, inizio con lo stesso modello e poi scrivo il processo al suo interno.

Questa mattina mi ritrovo a chiedermi se potrei effettivamente trasformare questo modello in una struttura in cui inserire il mio processo.Il mio modello di base è composto da sole 122 righe di codice.A causa dei diversi requisiti per ciascun processo, ad es.diverso numero di argomenti, diversi tipi di argomenti e diverse dipendenze (alcuni dipendono dai servizi Web, altri dai database ecc.) Non riesco a capire come impostare il mio modello di base per ricevere un processo iniettato.

Il cuore del modello è semplicemente un timer che si ferma quando viene inizializzato e avvia il processo, quindi riavvia il timer una volta completato il processo.Quindi aggiungo il metodo di processo e le eventuali dipendenze direttamente nel modello.

Qualcuno ha qualche idea su come farlo?Ho esaminato l'iniezione delle dipendenze e spesso la uso già per l'iniezione di cose come la connettività dell'archivio dati?Esiste un modo per inserire un delegato con un numero/tipo di parametri sconosciuto in una classe?Sto guardando questo in modo errato?

Questo è il modello che ho:

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

Quindi sto cercando di modificarlo in modo che il mio servizio Windows generi un nuovo TimedProcess e lo inietti con il processo che deve essere eseguito, rimuovendo così completamente il codice TimedProcess dal mio servizio Windows e facendo riferimento alla DLL.

Modificare:Grazie per l'aiuto di tutti.Mi sono reso conto che se spingo il mio metodo RunProcess() all'esterno della mia libreria TimedProcess e lo passo Quello come azione nel costruttore, questo semplifica tutto nel modo in cui stavo cercando:

[Semplificato per brevità]

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();
    }
}
È stato utile?

Soluzione

Un approccio qui sarebbe quello di utilizzare le variabili acquisite in modo che tutti i delegati essenzialmente diventino Action o forse Func<T> - e lasciano il resto al chiamante tramite la magia di metodi anonimi, variabili acquisite , ecc. ad es.

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

(avvertenza: attenzione per asincronizzazione / cattura - spesso una cattiva combinazione)

dove DoStuff accetta un Expression. Quindi quando invochi i <=> i parametri vengono aggiunti automaticamente ecc. In alcuni codici di libreria RPC ho adottato questo approccio nell'altro modo, usando <=> - quindi esprimo un'interfaccia di servizio (come di consueto) e quindi ho metodi come:

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

chiamato, ad esempio:

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

Quindi il chiamante dice cosa se abbiamo avuto un'implementazione di quell'interfaccia - tranne che non lo facciamo mai realmente; invece, separiamo <=> e osserviamo il metodo chiamato, args, ecc. e li passiamo al livello RPC. Se sei interessato, si discute di più qui o il codice è disponibile qui .

Altri suggerimenti

Un mezzo per avere un delegato che accetta una serie sconosciuta di parametri è passare una matrice di oggetti. È quindi possibile utilizzare la lunghezza dell'array come numero di parametri e poiché qualsiasi tipo è convertibile in un oggetto, è possibile passare qualsiasi cosa.

È possibile utilizzare un delegato senza parametri per rappresentare " service call " ;:

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

Per completezza, ecco alcune implementazioni di esempio.

Entrambi usano la metodologia del delegato incapsulata già discussa. Uno usa & Quot; params & Quot; e uno usa generici. Entrambi evitano il & Quot; asincrono / cattura & Quot; problema. In effetti, questo è molto simile a come vengono implementati gli eventi.

Scusa in anticipo, è tutto in un blocco di codice lungo. L'ho diviso in tre spazi dei nomi secondari:

  • FakeDomain (tiene per esempio beffe)
  • UsingParams (contiene l'implementazione che utilizza la parola chiave params)
  • UsingGenerics (contiene un'implementazione che utilizza generici)

Vedi sotto:

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

Se il parametro del metodo o la proprietà per il delegato è solo di tipo Delegato, è possibile utilizzare il metodo DynamicInvoke per chiamare il delegato, indipendentemente dalla sua firma. In questo modo:

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

Dovresti davvero essere in grado di utilizzare un delegato con tipizzazione forte, probabilmente un Func o un'azione che può prendere tutti i parametri necessari per passare al processo.

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

Personalmente, avrei creato un'interfaccia che tutti i processi dovevano implementare, quindi il servizio avrebbe scoperto tutti gli oggetti che la implementavano. In questo modo potevano fornire le loro esigenze di tempistica e un metodo fortemente tipizzato per chiamare il servizio. Qualcosa del genere:

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

Per la corruzione finirò lì, il tuo servizio potrebbe scoprire tutto il processo cercando le classi che implementano ITimerProcess, quindi creando un timer per ciascuno basato sulla proprietà Periodo che ciascuno espone. Il timer dovrebbe semplicemente chiamare PerformAction con tutti gli altri dati extra che vorresti passare.

Stai cercando:- Params Object [] ParameterValues

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top