Why does type inference not work in this code?

Say I have a service interface that looks like this:

public interface IFooService { FooResponse Foo(FooRequest request); } 

I would like to perform some cross-cutting issues when calling methods of such services; for example, I want unified query logging, performance logging, and error handling. My approach is to have a common Repository base class with the Invoke method, which takes care of calling the method and doing other things around it. My base class looks something like this:

 public class RepositoryBase<TService> { private Func<TService> serviceFactory; public RepositoryBase(Func<TService> serviceFactory) { this.serviceFactory = serviceFactory; } public TResponse Invoke<TRequest, TResponse>( Func<TService, Func<TRequest, TResponse>> methodExpr, TRequest request) { // Do cross-cutting code var service = this.serviceFactory(); var method = methodExpr(service); return method(request); } } 

It works great. However, my whole goal is to make a code cleaner that prevents type inference from working properly. For example, if I write a method like this:

 public class FooRepository : BaseRepository<IFooService> { // ... public BarResponse CallFoo(...) { FooRequest request = ...; var response = this.Invoke(svc => svc.Foo, request); return response; } } 

I get this compilation error:

Type arguments for a method ... cannot be taken out of use. Try explicitly specifying type arguments.

Obviously, I can fix this by changing my call to:

 var response = this.Invoke<FooRequest, FooResponse>(svc => svc.Foo, request); 

But I would like to avoid this. Is there a way to redo the code so that I can use type inference?

Edit:

It should also be mentioned that an earlier approach was to use the extension method; output type for this:

 public static class ServiceExtensions { public static TResponse Invoke<TRequest, TResponse>( this IService service, Func<TRequest, TResponse> method, TRequest request) { // Do other stuff return method(request); } } public class Foo { public void SomeMethod() { IService svc = ...; FooRequest request = ...; svc.Invoke(svc.Foo, request); } } 
+7
source share
3 answers

The question that is the heading of your question is "why type inference doesn't work in this code?" Let just the code in question. The script is at the heart of:

 class Bar { } interface I { int Foo(Bar bar); } class C { public static R M<A, R>(A a, Func<I, Func<A, R>> f) { return default(R); } } 

Call site

 CM(new Bar(), s => s.Foo); 

We must define two facts: what is A and R ? What information do we need to continue? For new Bar() corresponds to A , and s=>s.Foo corresponds to Func<I, Func<A, R>> .

It is clear that we can determine that A must be a Bar from this first fact. And, obviously, we can determine that s must be I So, now we know that (I s)=>s.Foo corresponds to Func<I, Func<Bar, R>> .

Now the question is: can we conclude that R is int by doing overload resolution on s.Foo in the lambda body?

Unfortunately, the answer is no. You and I can conclude, but the compiler does not. When we developed the type inference algorithm, we considered the possibility of adding this type of "multi-level" output of the lambda / delegate / method, but decided that this was too high a cost in favor of what it could bring.

Sorry, you're out of luck here; in general, conclusions that require "digging" more than one level of functional abstraction are not implemented in a method such as the C # method.

Why does this work when using extension methods?

Because the extension method does not have more than one level of functional abstraction. Case extension method:

 class C { public static R M<A, R>(I i, A a, Func<A, R> f) { ... } } 

with call site

 I i = whatever; CM(i, new Bar(), i.Foo); 

Now what is our information? We find that A Bar , as before. Now we have to determine that R knows that i.Foo maps to Func<Bar, R> . This is a simple overload resolution problem; we pretend there was an i.Foo(Bar) call and allow overload work. Overload resolution is returned and says i.Foo(Bar) returns int , so R is int .

Note that this type of output - involving a group of methods - should have been added in C # 3, but I messed up and we did not do it on time. In the end, we added this output to C # 4.

Also note that in order to successfully complete such an output, all parameter types must be output . We should only output the return type, because in order to know the return type, we must be able to do overload resolution, and to perform overload resolution we need to know all types of parameters. We do not make any nonsense, like "oh, the group of methods has only one method, so let it skip overload resolution and just let this method automatically win."

+12
source

After your last edit, we see that svc.Foo is a group of methods; which explains the failure of type inference. The compiler must know the type arguments so that it can select the correct Foo overload to convert the group of methods.

0
source

Why not just call the method directly? IE:

 public class ClassBase<TService> { protected Func<TService> _serviceFactory = null; public ClassBase(Func<TService> serviceFactory) { _serviceFactory = serviceFactory; } public virtual TResponse Invoke<TResponse>(Func<TService, TResponse> valueFactory) { // Do Stuff TService service = serviceFactory(); return valueFactory(service); } } 

Then theoretically you should do this:

 public class Sample : ClassBase<SomeService> { public Bar CallFoo() { FooRequest request = ... var response = Invoke(svc => svc.Foo(request)); return new Bar(response); } } 
0
source

All Articles