C# constraint generic parameter to a T<T2> for Fluent interface / compiler can't infer types
WCF client classes are typically setup like:
public class Client : ClientBase<IService>, IService
I want to extend these clients with an extension method that is Fluent, so that I can declare a using statement like:
using (new Client().WithCookies(...)) {}
Howev开发者_JAVA技巧er, I can't figure out a way to maintain the original Type of the caller, without creating some rather clunky calling syntax:
new Client().WithCookies<Client,IService>(...)
I'm not sure why the compiler can't infer the T based on what I've passed in, but it can't, based on the definition of the extension method:
public static T WithCookies<T, TChannel>(this T clientBase, IEnumerable<Cookie> cookies)
where T : ClientBase<TChannel>, TChannel
where TChannel : class
{
HttpRequestMessageProperty requestProperty = new HttpRequestMessageProperty();
requestProperty.Headers.Add(HttpCookieHeader, string.Join("; ", cookies.Select(c => c.ToCookieString(false))));
new OperationContext(clientBase.InnerChannel).OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestProperty;
return clientBase;
}
I know Eric Lippert in his blog, dismisses the notion of specifying a 'I don't care what the type argument to the generic is' (and for generally good reasons) http://blogs.msdn.com/b/ericlippert/archive/2008/05/19/a-generic-constraint-question.aspx
A psuedo-implementation would be something like:
public static T WithCookies<T>(this T clientBase, IEnumerable<Cookie> cookies)
where T : ClientBase<>
{
HttpRequestMessageProperty requestProperty = new HttpRequestMessageProperty();
requestProperty.Headers.Add(HttpCookieHeader, string.Join("; ", cookies.Select(c => c.ToCookieString(false))));
new OperationContext(clientBase.InnerChannel).OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestProperty;
return clientBase;
}
This is one scenario where it seems to me a good fit, as I don't care what TChannel is -- I'm not going to use it in my code, I just want to use any ClientBase<> as a constraint.
With that said, can anyone come up with a creative way to implement my Fluent requirement without the specification of the Types?
Keep in mind that if you don't return the original item passed in, you lose the ability to call service methods implemented in IService.
This was answered in another thread by Eric Lippert -
Generic extension method : Type argument cannot be inferred from the usage
The short answer is, compiler inference doesn't work like that unfortunately.
More details on his blog: http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx
Not an answer to the question, but something to be aware of if you manage to get this working...
If you use the using (new Client().WithCookies(...))
syntax and your WithCookies
method happens to throw an exception then the Client
instance won't be diposed.
The way to implement your pseudo-implementation would be via reflection. You could do this:
- Use reflection to look at
clientBase
and work out whatTChannel
is (by going up the type hierarchy and looking forClientBase<TChannel>
) - Obtain a specialized definition of
WithCookies
usingtypeof(YourClass).GetMethod("WithCookiesImpl").MakeGenericMethod(new[] { typeof(T), channelType })
- Invoke this specialized definition of
WithCookies
For me the question is, why can't the compiler infer the types in the call to T WithCookies<T, TChannel>
? It has the T : ClientBase<TChannel>
constraint on it; since T can only inherit from ClientBase
once, there can only be one TChannel
. (If ClientBase
had been an interface then there could have been more than one TChannel
.)
精彩评论