Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
168 views
in Technique[技术] by (71.8m points)

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

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 do this, but I find that every time I write a service to do this, I start with the same template and then write the process inside it.

This morning I find myself wondering if I could actually turn this template into a framework that I could inject my process into. My basic template is only 122 lines of code. Due to differing requirements for each process - i.e. different number of arguments, different argument types and different dependencies (some are dependent on Web Services, some on databases etc.) I can't figure out how to set up my basic template to receive an injected process.

The heart of the template is just a timer that stops when it initializes and starts the process and then restarts the timer once the process completes. I then add my process method and any dependencies right into the template.

Has anyone got any ideas how to do this? I've looked at dependency injection and often use it already for injection of things like data store connectivity? Is there a way to inject a delegate with an unknown number/type of parameters into a class? Am I looking at this incorrectly?

This is the template I have:

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

So I'm looking to modify it so that my Windows Service spawns a new TimedProcess and injects it with the process that needs running thereby removing the TimedProcess code from my Windows Service entirely and having it reference the DLL.

Edit: Thanks for everyone's help. I realized that if I push my RunProcess() method outside of my TimedProcess library and pass that in as an Action in the constructor, then this simplifies everything the way I was looking to:

[Simplified for brevity]

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();
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...