How to make delegates with variable number of arguments.

L

LRaiz

Can one of the gurus tell me if it is possible (and how) to do it in
c#?

I have a method that processes web requests using XmlTextReader. There
are many kinds of requests and a fair amount of code is repetitive
(initialization, cleanup, error handling, etc.) The only difference is
in processing of individual XmlElements. Thus I am trying to create a
utility which would accept as an argument a delegate to deal with
specifics of a particular request. Since processing is quite different
in every case it is tempting to have a variable number of arguments.
Here is how I attempt to do it.

void delegate ReadDelegate(XmlTextReader reader, params object[]
args);

void ProcessRequest(ReadDelegate dlg, params obj[] args)
{
....
XmlTextReader reader = ..
while( reader.Read() )
dlg(reader, args);
.....
}

So far so good but the problems comes later when I try to use it with
specific handlers that have non-variable number of arguments.

For example -
void ReadExample( XmlTextReader reader, string str)
{ ..}

The statement below would not complile
ProcessRequest( new ReadDelegate(ReadExample), new object[]
{"something"} )
ReadExample signature does not match ReadDelegate.

On one hand I don't want to repeat the code in ProcessRequest but on
the other hand various handlers must have various signatures with
specific number of arguments. Is there a way to handle the dilemma in
c#?
 
G

Göran Andersson

LRaiz said:
Can one of the gurus tell me if it is possible (and how) to do it in
c#?

I have a method that processes web requests using XmlTextReader. There
are many kinds of requests and a fair amount of code is repetitive
(initialization, cleanup, error handling, etc.) The only difference is
in processing of individual XmlElements. Thus I am trying to create a
utility which would accept as an argument a delegate to deal with
specifics of a particular request. Since processing is quite different
in every case it is tempting to have a variable number of arguments.
Here is how I attempt to do it.

void delegate ReadDelegate(XmlTextReader reader, params object[]
args);

void ProcessRequest(ReadDelegate dlg, params obj[] args)
{
....
XmlTextReader reader = ..
while( reader.Read() )
dlg(reader, args);
.....
}

So far so good but the problems comes later when I try to use it with
specific handlers that have non-variable number of arguments.

For example -
void ReadExample( XmlTextReader reader, string str)
{ ..}

The statement below would not complile
ProcessRequest( new ReadDelegate(ReadExample), new object[]
{"something"} )
ReadExample signature does not match ReadDelegate.

On one hand I don't want to repeat the code in ProcessRequest but on
the other hand various handlers must have various signatures with
specific number of arguments. Is there a way to handle the dilemma in
c#?

The signature of the method you want to use in the delegate has to match
the delegate exactly, so you can't use any construct like params to
accomodate for different signatures. However, you can use generics to
specify the arguments, then you only need one ProcessRequest method for
each number of arguments that you use.

public delegate void ReadDelegate<T1>(XmlTextReader reader, T1 arg1);
public delegate void ReadDelegate<T1, T2>(XmlTextReader reader, T1 arg1,
T2 arg2);

public void ProcessRequest<T1>(ReadDelegate<T1> action, T1 arg1) {
XmlTextReader reader = ...
while (reader.Read()) {
action(reader, arg1);
}
}

public void ProcessRequest<T1, T2>(ReadDelegate<T1, T2> action, T1 arg1,
T2 arg2) {
XmlTextReader reader = ...
while (reader.Read()) {
action(reader, arg1, arg2);
}
}

public void ReadExample(XmlTextReader reader, string str) {
...
}


ProcessRequest<string>(ReadExample, "...");
 
L

LRaiz

[...]
On one hand I don't want to repeat the code in ProcessRequest but on
the other hand various handlers must have various signatures with
specific number of arguments. Is there a way to handle the dilemma in
c#?

One option would be for you to not have a specific delegate type for  
ProcessRequest(), but just use the System.Delegate type.  Then in the  
ProcessRequest() method, put all the arguments into an array and pass the 
array to the Delegate.DynamicInvoke() method.  Since you don't need to  
match any specific predefined delegate type, you can just use the built-in  
generic "Action" delegate types when you actually create the delegates to 
pass for use in ProcessRequest().

Alternatively, you could just have all of your callback methods have the  
same signature: a single "params object[]".  Each method would then itself  
be responsible for casting/unboxing parameters passed to it.

Or, if for some reason you must have your callback methods each declare  
the arguments explicitly, you could provide wrappers that do the  
casting/unboxing before calling the actual callback methods.  You could 
even make these wrappers anonymous methods (but only if you expect to have  
to use each callback method in one place...if you need to reuse the  
wrappers, making them named methods would make more sense).

Pete

Thanks Pete. I like your suggestion of using Delegate.DynamicInvoke
and will give it a try. Actually after some digging I even found this
approach myself. But it is reassuring to hear that this is approach is
valid.
 
N

Nicholas Paldino [.NET/C# MVP]

Another option would be to take a delegate of type Action<XmlTextReader>
and pass ONLY the reader into the delegate.

Then, instead of passing the parameters, use the fact that anonymous
methods are closures to capture the state information you are passing in the
form of parameters in the delegate itself, and then invoke in
ProcessRequest.
 
L

LRaiz

    Another option would be to take a delegate of type Action<XmlTextReader>
and pass ONLY the reader into the delegate.

    Then, instead of passing the parameters, use the fact that anonymous
methods are closures to capture the state information you are passing in the
form of parameters in the delegate itself, and then invoke in
ProcessRequest.

--
          - Nicholas Paldino [.NET/C# MVP]
          - (e-mail address removed)


Can one of the gurus tell me if it is possible (and how) to do it in
c#?
I have a method that processes web requests using XmlTextReader. There
are many kinds of requests and a fair amount of code is repetitive
(initialization, cleanup, error handling, etc.) The only difference is
in processing of individual XmlElements.  Thus I am trying to create a
utility which would accept as an argument a delegate to deal with
specifics of a particular request. Since processing is quite different
in every case it is tempting to have a variable number of arguments.
Here is how I attempt to do it.
void delegate ReadDelegate(XmlTextReader reader, params object[]
args);
void ProcessRequest(ReadDelegate dlg, params obj[] args)
{
  ....
  XmlTextReader reader = ..
  while( reader.Read() )
      dlg(reader, args);
 .....
}
So far so good but the problems comes later when I try to use it with
specific handlers that have non-variable number of arguments.
For example -
void ReadExample( XmlTextReader reader, string str)
{ ..}
The statement below would not complile
ProcessRequest( new ReadDelegate(ReadExample), new object[]
{"something"} )
ReadExample signature does not match ReadDelegate.
On one hand I don't want to repeat the code in ProcessRequest but on
the other hand various handlers must have various signatures with
specific number of arguments. Is there a way to handle the dilemma in
c#?

Anonymous delegates turned out to be the best option because they
allow access to local variables declared by the caller of
ProcessRequest . Otherwise I was running into difficulties trying to
figure out how to pass "out" and "ref" arguments into DynamicInvoke.
With anonymous delegates I did not need to worry about variable number
of arguments either. I also did not have to use Action<T> though. I
leaned a lot during this exercise.
Thanks for the pointers.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top