Generics, delegates and type inference
-
Hi all, I'm trying to use a web service with authenticated access. The service returns a session token with every response and the token may change during the session. Operations on the service all follow a simple pattern: They take one argument representing the request, and return an object representing the response. Each operation has a specific req/response associated with it, but requests are derived from a common base class and responses from another. The base classes provide access to the headers, so they are sufficient to manage the session. The business object I'm writing that uses the service will execute code in multiple threads. Rather than duplicate the thread-synchronization and token-maintenance code everywhere I invoke a service operation, I thought I'd use generics and delegates so the generic method would implement all the things that should always happen and call the delegate to do the specifics. Since the service's methods all take a request derived from APIRequest and return a response derived from APIResponse, they conform to
Func<APIRequest, APIResponse>
. Some methods, for example logout, take a request object that contains no information except the request header that identifies the session. I would like to be able to use my generic method, called Invoke, like this:public void Logout() { if (!HasSession) return; var r = Invoke(globalService.logout); ... } void keepAlive() { // This runs on a background thread and makes a call to keep the session alive // if it's close to 20 minutes since the last request. TimeSpan margin = TimeSpan.FromSeconds(30); while (HasSession) { if (sessionLifeLeft > margin) Thread.Sleep(sessionLifeLeft - margin); Invoke(globalService.keepAlive); } }
I declared the following generic methods to make this possible (the implementation details aren't really important for our purposes here, but I include it as-is since I don't understand why it doesn't work the way I want it to):protected TResponse Invoke<TRequest, TResponse>(Func<TRequest, TResponse> method) where TRequest : APIRequest, new() where TResponse : APIResponse { return Invoke<TRequest, TResponse>(method, new TRequest()); } protected TResponse Invoke<TRequest, TResponse>(Func<TRequest, TResponse> method, TRequest req) where TRequest: APIRequest where TResponse: APIResponse { APIResponse r; lock (thi
-
Hi all, I'm trying to use a web service with authenticated access. The service returns a session token with every response and the token may change during the session. Operations on the service all follow a simple pattern: They take one argument representing the request, and return an object representing the response. Each operation has a specific req/response associated with it, but requests are derived from a common base class and responses from another. The base classes provide access to the headers, so they are sufficient to manage the session. The business object I'm writing that uses the service will execute code in multiple threads. Rather than duplicate the thread-synchronization and token-maintenance code everywhere I invoke a service operation, I thought I'd use generics and delegates so the generic method would implement all the things that should always happen and call the delegate to do the specifics. Since the service's methods all take a request derived from APIRequest and return a response derived from APIResponse, they conform to
Func<APIRequest, APIResponse>
. Some methods, for example logout, take a request object that contains no information except the request header that identifies the session. I would like to be able to use my generic method, called Invoke, like this:public void Logout() { if (!HasSession) return; var r = Invoke(globalService.logout); ... } void keepAlive() { // This runs on a background thread and makes a call to keep the session alive // if it's close to 20 minutes since the last request. TimeSpan margin = TimeSpan.FromSeconds(30); while (HasSession) { if (sessionLifeLeft > margin) Thread.Sleep(sessionLifeLeft - margin); Invoke(globalService.keepAlive); } }
I declared the following generic methods to make this possible (the implementation details aren't really important for our purposes here, but I include it as-is since I don't understand why it doesn't work the way I want it to):protected TResponse Invoke<TRequest, TResponse>(Func<TRequest, TResponse> method) where TRequest : APIRequest, new() where TResponse : APIResponse { return Invoke<TRequest, TResponse>(method, new TRequest()); } protected TResponse Invoke<TRequest, TResponse>(Func<TRequest, TResponse> method, TRequest req) where TRequest: APIRequest where TResponse: APIResponse { APIResponse r; lock (thi
You need to cast the return value because the compiler does not try to make any assumptions about what may or may not happen to r between the time you store the method return to the time you return it from your method. Since you only intend to put TResponse in, why not just change r to be of type TResponse instead of APIResponse. As for the second part, how is
globalService.logout
declared? -
You need to cast the return value because the compiler does not try to make any assumptions about what may or may not happen to r between the time you store the method return to the time you return it from your method. Since you only intend to put TResponse in, why not just change r to be of type TResponse instead of APIResponse. As for the second part, how is
globalService.logout
declared?Thank you, that's the sort of simple and rather logical explanation I dared not hope for! The declaration is
public LoginResp login(LoginReq request)
LoginResp : APIResponse LoginReq : APIRequest However, I've found some worse caveats to deal with. The API has been divided into several services, but one needs data from one service to make requests to the other service - as well as the same session token for both as far as I understand, which means one cannot reliably call both services concurrently. A lot of types defined by the WSDL exist in both services. But of course, when I just generate the proxies all these types end up with redundant definitions in different namespaces. Worse, to invoke an operation in service2 that needs input obtained from service1, I have to waste time and space at run-time as well as write rather boring code like this:public Service2.SomeType ToService2Type(Service1.SomeType obj) { Service2.SomeType result = new Service2.SomeType(); // Primite types: Just copy, as they are defined outside the service namespace // and we thus have type affinity. result.Property1 = obj.Property1; result.Property2 = obj.Property2; // Anything else, "convert" between types that are really the same... result.Property3 = ToService2Type(obj.Property3); ... return result; } public Service2.SomeOtherType ToService2Type(Service1.SomeOtherType) { ... }
Writing the code is a bore, but I think it's even worse that I cannot avoid the run-time overhead. I guess if I go into the generated proxy code I can change attributes all over the place and use the same types with both services where in fact they match the soap messages, but doing so would be even more of a chore and what if I need to regenerate the proxies some day? I could perhaps write a separate app that uses reflection to discover the types and generate the code for all those ToService2Type methods, but that of course doesn't remove the run-time overhead. Not quite as nice as I hoped this API... -
Thank you, that's the sort of simple and rather logical explanation I dared not hope for! The declaration is
public LoginResp login(LoginReq request)
LoginResp : APIResponse LoginReq : APIRequest However, I've found some worse caveats to deal with. The API has been divided into several services, but one needs data from one service to make requests to the other service - as well as the same session token for both as far as I understand, which means one cannot reliably call both services concurrently. A lot of types defined by the WSDL exist in both services. But of course, when I just generate the proxies all these types end up with redundant definitions in different namespaces. Worse, to invoke an operation in service2 that needs input obtained from service1, I have to waste time and space at run-time as well as write rather boring code like this:public Service2.SomeType ToService2Type(Service1.SomeType obj) { Service2.SomeType result = new Service2.SomeType(); // Primite types: Just copy, as they are defined outside the service namespace // and we thus have type affinity. result.Property1 = obj.Property1; result.Property2 = obj.Property2; // Anything else, "convert" between types that are really the same... result.Property3 = ToService2Type(obj.Property3); ... return result; } public Service2.SomeOtherType ToService2Type(Service1.SomeOtherType) { ... }
Writing the code is a bore, but I think it's even worse that I cannot avoid the run-time overhead. I guess if I go into the generated proxy code I can change attributes all over the place and use the same types with both services where in fact they match the soap messages, but doing so would be even more of a chore and what if I need to regenerate the proxies some day? I could perhaps write a separate app that uses reflection to discover the types and generate the code for all those ToService2Type methods, but that of course doesn't remove the run-time overhead. Not quite as nice as I hoped this API...WSDL and all of that is way outside of my experience (and doesn't seem to be something you are really having questions about, just complaints), so I won't speak to that. Doing a search for "generic delegate inference" showed other people that were having the same problem as you, along with some explanations I will rephrase here. I'm sure I've run into the problem before and just don't remember where. The problem seems to come down to the compiler having to infer too many things at once. The rest of this post is how I assume the general flow of compilation goes, but I am by no means an authority on the compiler. When you say
Invoke(globalService.logout)
, the compiler first has to guess what type to makeglobalService.logout
, before it can determine what types to use forInvoke
. It will then look to the method signature ofInvoke
to try to determine what type of delegate to create from the method groupglobalService.logout
. It will see that it is based on a generic parameter, so it can't make any assumptions there. In this case, there is only one possible overload of logout, but there could be more logout definitions, and then there would be no way to know which one you want. The compiler team probably decided it wasn't worth adding the special case for when you have only one overload. One reason against that feature would be that adding an overload to the method would suddenly break previously working code. Since there are no other parameters, the compiler has no more information to help it determine what types to fill in for the generic declaration and gives up. If you were to change Invoke to beInvoke<TRequest, TRepsone>(Func<TRequest, TResponse> method, TRequest param)
, the compiler would then figure it out from the second parameter. It can determine this because you are not allowed to overload methods solely by return type. Thus when it sees the second parameter is, for example,string
, the first parameter becomesFunc<string, TResponse>
which is apparently just enough for it to do the rest of the work itself. As you might guess, givingInvoke
a parameter ofTResponse
will not clear up the error (because you could still have multiple definitions that would match).