2012-04-19 14 views
7

Powiedzmy mam interfejs usługi, który wygląda tak:Dlaczego w niniejszym kodzie nie działa inferencja typów?

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

chciałbym spełnić pewne obawy przekrojowe podczas wywoływania metody na usługi takie jak; na przykład chcę ujednolicić rejestrowanie żądań, rejestrowanie wydajności i obsługę błędów. Moje podejście jest mieć wspólną bazę „klasy repozytorium” z metody Invoke że dba o wywołanie metody i robi inne rzeczy wokół niego moja klasa podstawowa wygląda mniej więcej tak:..

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

Działa to dobrze, jednak całe moje celem dokonywania czystsze kod jest udaremnione przez fakt, że typ wnioskowanie nie działa zgodnie z oczekiwaniami na przykład, jeśli mam napisać metodę tak:.

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

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

otrzymuję ten błąd kompilacji:

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

Oczywiście, mogę go naprawić, zmieniając moje wezwanie do:

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

Ale chciałbym, aby tego uniknąć. Czy istnieje sposób na przerobienie kodu, aby móc skorzystać z wnioskowania o typie?

Edit:

Należy również wspomnieć, że wcześniejsze podejście było użyć metodę rozszerzenia; typ wnioskowanie o to działało:

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

Odpowiedz

12

Pytanie który to tytuł jest pytanie „dlaczego nie jest typ wnioskowania pracy w tym kodzie” Po prostu kod, o którym mowa. Scenariusz jest w jego sercu:

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

Witryna Połączenie jest

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

Musimy ustalić dwa fakty: jakie są A i R? Jakie informacje musimy przejść? Ten numer new Bar() odpowiada A i s=>s.Foo odpowiada Func<I, Func<A, R>>.

Oczywiście możemy stwierdzić, że A musi być Bar z tego pierwszego faktu. I wyraźnie możemy stwierdzić, że s musi być I. Więc teraz wiemy, że (I s)=>s.Foo odpowiada Func<I, Func<Bar, R>>.

Teraz pytanie brzmi: czy możemy wywnioskować, że R jest int, wykonując rozdzielczość przeciążania na s.Foo w ciele lambda?

Niestety, odpowiedź brzmi: nie. Ty i ja możemy to wnioskować, ale kompilator tego nie robi. Kiedy zaprojektowaliśmy algorytm wnioskowania o typie, rozważaliśmy dodanie tego rodzaju "wielopoziomowego" wnioskowania lambda/delegata/grupy metod, ale zdecydowaliśmy, że byłby to zbyt wysoki koszt z korzyścią, którą by dał.

Niestety, masz pecha tutaj; Ogólnie rzecz biorąc, wnioskowania, które wymagałyby "przekopywania się" przez więcej niż jeden poziom funkcjonalnej abstrakcji, nie są wykonywane w wnioskowaniu typu C#.

Why did this work when using extension methods, then?

Ponieważ metoda rozszerzenia nie ma więcej niż jeden poziom abstrakcji funkcjonalnej. Sprawa metoda rozszerzenie:

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

w miejscu połączenia

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

Teraz jakie informacje mamy? Możemy wywnioskować, że A jest Bar jak poprzednio. Teraz musimy wywnioskować, co R wie, że i.Foo mapuje na Func<Bar, R>. Jest to prosty problem z przeciążeniem; udajemy, że było połączenie z i.Foo(Bar) i pozwoliliśmy, aby jego rozdzielczość działała. Występuje ochrona przed przeciążeniem, która mówi, że i.Foo(Bar) zwraca , więc jest .

Należy zauważyć, że ten rodzaj wnioskowania - obejmujący grupę metod - został dodany do C# 3, ale pomieszałem i nie udało się go wykonać na czas. Skończyło się dodanie tego rodzaju wnioskowania do C# 4.

Zauważ też, że dla tego rodzaju wnioskowania, aby odnieść sukces, wszystkie typy parametrów muszą być już wywnioskować. Musimy zwracać uwagę na typ zwracany, ponieważ aby poznać typ zwracany, musimy być w stanie wykonać analizę przeciążenia i aby uzyskać rozdzielczość przeciążania, musimy znać wszystkie typy parametrów. Nie robimy żadnych bzdur takich jak "o, grupa metod ma tylko jedną metodę, pomińmy więc rozdzielczość przeciążania i pozwólmy, aby ta metoda automatycznie wygrywała".

+0

Widzę; to ma sens. – Jacob

0

Po ostatniej edycji, widzimy, że svc.Foo jest grupa metoda; to wyjaśnia błąd wnioskowania typu. Kompilator musi znać argumenty typu, aby mógł wybrać właściwe przeciążenie dla konwersji grupy metod.

+0

@Jacob zobacz edytowane pytanie. – phoog

+0

Dodam interfejs 'IFooService' do mojego pytania; jest to metoda kompatybilna z 'Func '. – Jacob

+0

@Jacob Kompilator nie może wywnioskować typów konwersji grupy metod, ponieważ musi znać typy, aby wybrać prawidłowe przeciążenie 'Foo'. Tutaj mamy tylko jedno przeciążenie, oczywiście, ale potrzeba rozwiązania ogólnego problemu wyjaśnia w tym przypadku niepowodzenie inferencji typu. – phoog

0

Dlaczego po prostu nie wywołać metody bezpośrednio? 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); 
    } 
} 

Następnie należy teoretycznie być w stanie to zrobić:

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

Powodem, dla którego chcę przekazać obiekt żądania jako argument, jest to, że żądanie może zostać przekształcone do postaci szeregowej i zarejestrowane. Mogłem zrobić oba, wywołać metodę bezpośrednio i przekazać obiekt żądania, ale chcę uniknąć tej nadmiarowości. – Jacob

+0

Zawsze można przekształcić 'Func' w' Wyrażenie ', a następnie sprawdzić wyrażenie do serializacji. – Tejs

+0

Tak, jest to opcja, ale wolałbym uniknąć wydajności za pomocą refleksji. – Jacob

Powiązane problemy