2012-04-19 6 views
7

Digamos que tengo una interfaz de servicio que tiene este aspecto:¿Por qué la inferencia de tipo no funciona en este código?

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

me gustaría que cumplir con algunos conceptos transversales al llamar a métodos en los servicios de este tipo; por ejemplo, quiero el registro unificado de solicitudes, el registro de rendimiento y el manejo de errores. Mi enfoque es tener una base común "clase repositorio' con un método Invoke que se encarga de invocar el método y hacer otras cosas que mi clase base es como la siguiente:..

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); 
    } 
} 

Esto funciona bien sin embargo , toda mi objetivo de hacer el código más limpio es frustrado por el hecho de que la inferencia de tipos no está funcionando como se espera, por ejemplo, si escribo un método como este:.

public class FooRepository : BaseRepository<IFooService> 
{ 
    // ... 

    public BarResponse CallFoo(...) 
    { 
     FooRequest request = ...; 
     var response = this.Invoke(svc => svc.Foo, request); 
     return response; 
    } 
} 

consigo este error de compilación:

The type arguments for method ... cannot be inferred from the usage. Try specifying the type arguments explicitly.

Obviamente, puedo solucionarlo cambiando mi llamada a:

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

Pero me gustaría evitar esto. ¿Hay alguna manera de volver a trabajar el código para que pueda aprovechar la inferencia de tipo?

Editar:

También debería mencionar que un enfoque anterior fue utilizar un método de extensión; inferencia de tipos para este funcionó:

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); 
    } 
} 

Respuesta

12

La pregunta que es el título de su pregunta es "¿por qué no se la inferencia de tipos trabajando en este código" Vamos simplemente al código en cuestión. El escenario está en su corazón:

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); 
    } 
} 

El sitio de llamada es

C.M(new Bar(), s => s.Foo); 

Debemos determinar dos hechos: ¿cuáles son A y R? ¿Qué información tenemos que seguir? Que new Bar() corresponde a A y s=>s.Foo corresponde a Func<I, Func<A, R>>.

Claramente podemos determinar que A debe ser Bar desde ese primer hecho. Y claramente podemos determinar que s debe ser I. Entonces ahora sabemos que (I s)=>s.Foo corresponde a Func<I, Func<Bar, R>>.

Ahora la pregunta es: ¿podemos inferir que R es int haciendo una resolución de sobrecarga en s.Foo en el cuerpo de la lambda?

Lamentablemente, la respuesta es no. Tú y yo podemos hacer esa inferencia, pero el compilador no. Cuando diseñamos el algoritmo de inferencia de tipo consideramos agregar este tipo de inferencia de lambda/delegado/grupo de múltiples niveles, pero decidimos que era un costo demasiado alto por el beneficio que conferiría.

Disculpe, no tiene suerte aquí; en general, las inferencias que requerirían "explorar" más de un nivel de abstracción funcional no se hacen en la inferencia del tipo de método C#.

Why did this work when using extension methods, then?

Porque el método de extensión no tiene más de un nivel de abstracción funcional. El caso es el método de extensión:

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

con un sitio llamado

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

Ahora qué información tenemos? Deducimos que A es Bar como antes. Ahora debemos deducir que R sabe que i.Foo se mapea a Func<Bar, R>. Este es un problema de resolución de sobrecarga directa; pretendemos que hubo una llamada al i.Foo(Bar) y permitimos que la resolución de sobrecarga haga su trabajo. La resolución de sobrecarga vuelve y dice que i.Foo(Bar) devuelve int, por lo que R es int.

Tenga en cuenta que este tipo de inferencia, que involucra un grupo de métodos, fue pensado para ser agregado a C# 3, pero cometí un error y no logramos hacerlo a tiempo. Terminamos agregando ese tipo de inferencia a C# 4.

Tenga en cuenta también que para que este tipo de inferencia tenga éxito, todos los tipos de parámetros ya deben inferirse. Debemos inferir solo el tipo de devolución, porque para conocer el tipo de devolución, debemos poder hacer una resolución de sobrecarga, y para hacer una resolución de sobrecarga, debemos conocer todos los tipos de parámetros. No hacemos tonterías como "oh, el grupo de métodos solo tiene un método, así que salteemos la resolución de sobrecarga y simplemente dejemos que el método gane automáticamente".

+0

Veo; eso tiene sentido. – Jacob

0

Después de su última edición, vemos que es un grupo svc.Foo método; eso explica el error de inferencia tipo. El compilador necesita conocer los argumentos de tipo para poder elegir la sobrecarga Foo correcta para la conversión del grupo de métodos.

+0

@Jacob, consulte la pregunta editada. – phoog

+0

Añadiré la interfaz 'IFooService' a mi pregunta; es un método que es compatible con 'Func '. – Jacob

+0

@Jacob El compilador no puede inferir los tipos para una conversión de grupo de métodos porque necesita conocer los tipos para seleccionar la sobrecarga correcta de 'Foo'. Aquí solo tiene una sobrecarga, por supuesto, pero la necesidad de resolver el problema general explica la falla de la inferencia de tipo en este caso. – phoog

0

¿Por qué no simplemente invocar el método directamente? 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); 
    } 
} 

Entonces debería teóricamente ser capaz de hacer esto:

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

El motivo por el que deseo pasar el objeto de solicitud como argumento es para que la solicitud se pueda serializar y registrar. Podría hacer ambas cosas, llamar al método directamente y pasar el objeto de solicitud, pero quiero evitar esta redundancia. – Jacob

+0

Siempre puede hacer que el 'Func' se convierta en una 'Expresión ' y luego inspeccionar la expresión de serialización. – Tejs

+0

Sí, esa es una opción, pero preferiría evitar el impacto de rendimiento del uso de la reflexión. – Jacob

Cuestiones relacionadas