Como posso criar uma classe para receber um delegado com um número desconhecido de parâmetros?

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

  •  05-07-2019
  •  | 
  •  

Pergunta

Eu continuamente encontrar-me ter de escrever serviços do Windows cronometrados que poll fora filas ou corrida cronometrada processos. Eu, consequentemente, têm um modelo bastante robusto pelo qual para fazer isso, mas eu acho que cada vez que eu escrever um serviço para fazer isso, eu começo com o mesmo modelo e, em seguida, escrever o processo dentro dela.

Esta manhã eu encontro-me perguntando se eu poderia realmente transformar esse modelo em um quadro que eu poderia injetar o meu processo em. Meu modelo básico é de apenas 122 linhas de código. Devido a diferentes requisitos para cada processo - ou seja, número diferente de argumentos, argumentos de tipos diferentes e diferentes dependências (alguns são dependentes de Web Services, alguns em bancos de dados etc.) Eu não consigo descobrir como configurar meu modelo básico para receber um processo injectado.

O coração do modelo é apenas um temporizador que pára quando ele inicializa e começa o processo e, em seguida, reinicia o temporizador uma vez que os concluída processo. Em seguida, adicione o meu método de processo e quaisquer dependências para a direita no template.

Alguém tem alguma idéia de como fazer isso? Eu olhei para injeção de dependência e muitas vezes usá-lo já para injecção de coisas como a conectividade de armazenamento de dados? Existe uma maneira de injetar um delegado com um número desconhecido / tipo de parâmetros para uma classe? Am I olhar para esta forma incorrecta?

Este é o modelo que eu tenho:

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

Então, eu estou olhando para modificá-lo para que meus desova de Serviços do Windows um novo TimedProcess e injeta com o processo que necessidades correndo assim removendo o código TimedProcess do meu Windows Service inteiramente e ter que fazer referência a DLL.

Editar : Obrigado pela ajuda de todos. Eu percebi que se eu empurrar o meu RunProcess () método fora da minha biblioteca TimedProcess e passar que em como uma Ação no construtor, então este simplifica tudo do jeito que eu estava olhando para:

[Simplificado para abreviar]

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

Solução

Uma abordagem aqui seria usar variáveis ??capturadas de modo que todos os delegados essencialmente Action tornar-se ou talvez Func<T> - e deixar o resto para o chamador através da magia de métodos anônimos, variáveis ??capturados, etc - ou seja,

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

(advertência: atente para async / captura - muitas vezes uma combinação ruim)

onde DoStuff aceita um Action. Então, quando você invocar o Action os parâmetros são automaticamente adicionados etc. Em alguns código da biblioteca RPC Eu tomei esta abordagem para o outro lado, usando Expression - então eu expressar uma interface de serviço (como normal), e depois ter métodos como:

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

chamado, por exemplo:

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

Em seguida, o chamador diz o que se tivemos uma implementação dessa interface - exceto nós nunca realmente fazer; em vez disso, puxe o Expression distante e olhar para o método que está sendo chamado, os argumentos, etc - e passá-los para a camada RPC. Se você estiver interessado, é discutido mais aqui , ou o código está disponível aqui .

Outras dicas

Uma forma de ter um delegado que leva um conjunto desconhecido de parâmetros é passar um array de objetos. Você pode então usar o comprimento do array como o número de parâmetros, e uma vez que qualquer tipo é convertível em um objeto, você pode passar qualquer coisa.

Você pode usar um delegado sem parâmetros para representar a "chamada de serviço":

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

Para completar, aqui está alguns exemplos de implementações.

Tanto o uso da metodologia delegado envolto já foi discutido. Um usa "params" e uma utilidades genéricos. Ambos evitar o "async / capture" problema. Na verdade, isso é muito semelhante à forma como os eventos são implementados.

Desculpe antecipadamente, é tudo em um bloco de código longo. Eu dividi-lo em três subnamespaces:

  • FakeDomain (detém simulações por exemplo)
  • UsingParams (detém implementação que usos params palavra-chave)
  • UsingGenerics (detém aplicação que usa os genéricos)

Veja abaixo:

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 o seu parâmetro de método ou propriedade para o delegado é apenas do tipo delegado que você pode usá-lo de forma DynamicInvoke chamar o delegado, não importa o que de assinatura é. Como assim:

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

Você realmente deve ser capaz de usar uma forte delegado digitado, provavelmente, um Func ou ação que pode tomar quaisquer parâmetros que você precisa para passar para o proccess.

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

Pessoalmente, eu iria criar uma interface que todos os processos tiveram que implementar, em seguida, ter o serviço de descobrir todos os objetos que implementam. Dessa forma, eles poderiam fornecer suas necessidades de tempo e um método fortemente tipado para chamada para o serviço. Algo parecido com isto:

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

Para brievity eu vou terminá-lo lá, o serviço poderia descover todo o processo, procurando por classes que implementam ITimerProcess, em seguida, criar um temporizador para cada uma baseada na propriedade Período cada expõe. O temporizador apenas teria que chamar PerformAction com quaisquer outros dados extras que você gostaria de passar.

Você está procurando: - params Object [] ParameterValues ??

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top