Comment concevoir une classe pour recevoir un délégué dont le nombre de paramètres est inconnu?

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

  •  05-07-2019
  •  | 
  •  

Question

Je suis continuellement obligé d'écrire des services Windows temporisés qui interrogent les files d'attente en dehors ou exécutent des processus chronométrés. J'ai donc un modèle assez robuste pour le faire, mais je constate que chaque fois que j'écris un service pour le faire, je commence par le même modèle, puis j'écris le processus à l'intérieur.

Ce matin, je me demande si je pourrais réellement transformer ce modèle en un cadre dans lequel je pourrais injecter mon processus. Mon modèle de base ne contient que 122 lignes de code. En raison des exigences différentes pour chaque processus (nombre d'arguments, types d'arguments et dépendances différents (certains dépendent de Web Services, d'autres de bases de données, etc.), je ne sais pas comment configurer mon modèle de base pour recevoir un message. processus injecté.

Le cœur du modèle est simplement un minuteur qui s’arrête lorsqu’il initialise et démarre le processus, puis le redémarre une fois le processus terminé. J'ajoute ensuite ma méthode de traitement et toutes les dépendances directement dans le modèle.

Quelqu'un a-t-il des idées pour faire cela? J'ai déjà examiné l'injection de dépendance et je l'utilise souvent déjà pour l'injection d'éléments comme la connectivité de magasin de données? Est-il possible d'injecter un délégué avec un nombre inconnu / type de paramètres dans une classe? Est-ce que je regarde mal?

Voici le modèle que j'ai:

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

Je souhaite donc le modifier afin que mon service Windows génère un nouveau TimedProcess et lui injecte le processus à exécuter, supprimant ainsi entièrement le code TimedProcess de mon service Windows et le référençant à la DLL.

Modifier : merci de votre aide. Je me suis rendu compte que si je poussais ma méthode RunProcess () en dehors de ma bibliothèque TimedProcess et que je transmettais cela en tant qu'Action dans le constructeur, cela simplifierait tout le chemin que je recherchais:

[Simplicité simplifiée]

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();
    }
}
Était-ce utile?

La solution

Une approche consiste à utiliser des variables capturées de sorte que tous les délégués deviennent essentiellement ou peut-être Action - et laissent le reste à l'appelant via la magie des méthodes anonymes, des variables capturées. , etc. - c.-à-d.

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

(avertissement: faites attention à l'async / capture - souvent une mauvaise combinaison)

Func<T> accepte un DoStuff. Ensuite, lorsque vous appelez le Expression, les paramètres sont automatiquement ajoutés, etc. Dans certains codes de bibliothèque RPC, j’ai adopté cette approche dans l’autre sens, avec <=> - pour exprimer une interface de service (comme d’habitude), puis disposer de méthodes. comme:

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

appelé, par exemple:

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

Ensuite, l'appelant dit quoi si nous avions une implémentation de cette interface - sauf que nous ne le faisons jamais réellement; au lieu de cela, nous séparons <=> et examinons la méthode appelée, les arguments, etc. - et les transmettons à la couche RPC. Si vous êtes intéressé, il est discuté plus ici , ou le code est disponible ici . .

Autres conseils

Pour déléguer un ensemble de paramètres inconnu à un délégué, vous pouvez transmettre un tableau d'objets. Vous pouvez ensuite utiliser la longueur du tableau comme nombre de paramètres, et comme tout type est convertible en objet, vous pouvez tout transmettre.

Vous pouvez utiliser un délégué sans paramètre pour représenter le " appel de service ":

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

Pour être complet, voici quelques exemples d'implémentations.

Les deux utilisent la méthodologie de délégation intégrée déjà décrite. On utilise & Quot; params & Quot; et on utilise des génériques. Les deux évitent le & Quot; async / capture & Quot; problème. En fait, cela ressemble beaucoup à la façon dont les événements sont implémentés.

Désolé, tout est dans un long bloc de code. Je l'ai divisé en trois sous-espaces de noms:

  • FakeDomain (conserve les répliques par exemple)
  • UsingParams (contient l'implémentation qui utilise le mot clé params)
  • UsingGenerics (conserve l'implémentation qui utilise des génériques)

Voir ci-dessous:

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 votre paramètre de méthode ou votre propriété pour le délégué est juste du type Délégué, vous pouvez utiliser sa méthode DynamicInvoke pour appeler le délégué, quelle que soit sa signature. Comme si:

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

Vous devriez vraiment pouvoir utiliser un délégué de type fort, probablement un Func ou une Action pouvant prendre tous les paramètres nécessaires pour être transmis au processus.

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

Personnellement, je créerais une interface que tous les processus devaient implémenter, puis le service découvrait tous les objets qui l'implémentaient. De cette façon, ils pourraient fournir leurs besoins de synchronisation et une méthode fortement typée pour appeler le service. Quelque chose comme ça:

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

Par souci de brièveté, je vais y mettre fin, votre service peut découvrir tout le processus en recherchant les classes qui implémentent ITimerProcess, puis en créant un minuteur pour chacune, basé sur la propriété Period exposée. Le minuteur devra simplement appeler PerformAction avec toute autre donnée supplémentaire que vous souhaitez transmettre.

Cherchez-vous: -  objet params [] paramètreValeurs

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top