Recently I spotted an interesting question on one of the forums. The person asking this question was conserned if it's possible to have some solution for creating some kind of universal wrapper around third-party API. There was one condition. Sometimes API calls throw an exception, but this doesn't necessarily means that method failed, sometimes the service that was queried in this API method was busy. In this case he wanted to repeat the call number of times before throwind an exception about API failure. This task looked like a perfect example to use generic delegates from C# 2.0. So, I refreshed my memories by reading MSDN articles on generics (I suggest you to read them too, especially if you are not familiar with generics terminology) and started on coding. Here is result of my "titanic work" on generics that actually combines generic type, generic method and generic delegate (isn't it just too much generic in one sentence
):
class APICaller<P, R> where R : new()
{
public const int MAX_API_RETRY_ATTEMPTS = 2;
public delegate R APICall<T>(T param);
public R ExecuteAPI(APICall<P> func, P param)
{ return ExecuteAPI(func, param, MAX_API_RETRY_ATTEMPTS); }
public R ExecuteAPI(APICall<P> func, P param, int ex_count)
{
int iAttempts = 0;
R response = new R();
while (true)
{
try
{
iAttempts++;
Console.Write("Attempt " + iAttempts.ToString());
response = func(param);
Console.WriteLine(" successfull!");
break;
}
catch (Exception except)
{
Console.WriteLine(" failed!");
// Allow a max number of failures - then die.
if (iAttempts > ex_count)
{
Console.WriteLine("Max number of attempts reached!");
throw except;
}
}
} // while true;
return response;
}
}
Let's see what we have here. First of all, I declare generic type APICaller (APICaller<P, R> where R : new()) with two type parameters: P - type of method's parameter , R - type of method's return value. Modifier (where R : new()) says that instance of this type parameter could be created using new() operator. We will need this to create default return value. Then I define public integer const inside this generic type (const int MAX_API_RETRY_ATTEMPTS = 2). It will serve us as a default value for counter of attеmpts. Then there goes the most fascinating part of this declaration - generic delegate type APICall (public delegate R APICall<T>(T param)). Let's explain this string in details. Modifiers public and delegate say that this is a declaration of a public delegate type with the name APICall that takes type T as an input parameter and return result of a type R. That's it, nothing complicated.
Then I declare generic method ExecuteAPI (public R ExecuteAPI(APICall<P> func, P param)). This declaration says that public method ExecuteAPI takes two parameters. First parameter is func of previously defined APICall delegate type (here goes a little trick, I pass type parameter P as a type argument for delegate). Second parameter is param of type P. I used two declarations of method ExecuteAPI so that it will be possible to use default value for parameter ex_count (this is a standard pattern in C# since it does not allow you to setup a default value for method parameters). Then I only mention two rows inside this method: declaration of response variable (R response = new R()), that's the place where we use new() operator mentioned above; actual invocation of delegate (response = func(param)).
To use this construction you have to do two things. First, create a constructed type:
APICaller<String, String> foo_string_caller = new APICaller<String, String>();
Second, call ExecuteAPI method by passing actual API function (e.g API.FooString):
foo_string_caller.ExecuteAPI(API.FooString, "str");
That's it!
To download full source of this example use the link below:
Program.zip (1.12 KB)
P.S. I found three interesting articles on delegates on Julien Couvreur's programming blog:
The dark side of C# Delegates
C# Delegates strike back
C# events vs. delegates
Take a look. It's not about generics but a fun reading anyway 