2012-04-19 52 views
7

比方說,我有一個服務接口,看起來像這樣:爲什麼在這段代碼中不能使用類型推斷?

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

我想呼籲喜歡這些服務的方法時,以滿足一些橫切關注點;例如,我想要統一的請求記錄,性能記錄和錯誤處理。我的做法是有一個共同的基礎「庫」與Invoke方法,它調用的方法和它周圍做其他事情的護理類我的基類看起來是這樣的:。

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

這正常不過,我的整個使代碼更清潔的目標是通過類型推斷工作不正常的事實挫敗例如,如果我寫這樣一個方法:

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

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

我得到這個編譯錯誤:

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

很顯然,我可以改變我的電話來解決它:

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

,但我想避免這種情況。有沒有辦法重寫代碼,以便我可以利用類型推斷?

編輯:

我還要提到的是較早的方法是使用一個擴展方法;此工作的類型推斷:

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

回答

12

的問題是你的問題的標題是「爲什麼沒有類型推斷在此代碼的工作」讓我們簡單介紹一下有關的代碼。該方案是在它的心臟:

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

調用點是

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

我們必須確定兩個事實:什麼是AR?我們需要做什麼信息? new Bar()對應於A,而s=>s.Foo對應於Func<I, Func<A, R>>

很明顯,我們可以確定A必須從第一個事實Bar。顯然,我們可以確定s必須是I。所以我們現在知道(I s)=>s.Foo對應於Func<I, Func<Bar, R>>

現在的問題是:我們可以推斷Rint通過在lambda體內執行s.Foo重載分辨率?

不幸的是,答案是否定的。你和我可以做這個推斷,但編譯器不會。當我們設計類型推斷算法時,我們考慮添加這種「多級」lambda /代理/方法組推理,但認爲它會帶來的好處成本太高。

對不起,你在這裏運氣不好;一般而言,在C#方法類型推斷中不會進行需要「挖掘」多個功能抽象級別的推理。

Why did this work when using extension methods, then?

因爲擴展方法沒有超過一個功能抽象級別。擴展方法的情況是:

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

與調用點

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

現在什麼信息,我們有嗎?我們推斷A與以前一樣是Bar。現在我們必須推導出R知道i.Foo映射到Func<Bar, R>。這是一個直接的重載解決問題;我們假裝有一個電話i.Foo(Bar)並讓重載決議完成其工作。過載分辨率回來並且說i.Foo(Bar)返回int,所以Rint

請注意,這種推理 - 涉及一個方法組 - 旨在將其添加到C#3中,但我搞砸了,但我們沒有及時完成。我們最終添加了對C#4的這種推斷。

還要注意,對於這種推斷成功,必須已經推斷出所有參數類型。我們必須推導出只有的返回類型,因爲要知道返回類型,我們必須能夠做重載決議,並且要做重載解析,就必須知道所有的參數類型。我們不會做任何廢話,比如「哦,方法組只有一個方法,所以讓我們跳過重載解決方案,讓這個方法自動取勝」。

+0

我明白了;這就說得通了。 – Jacob 2012-04-19 21:28:34

0

在您上次編輯之後,我們看到svc.Foo是一個方法組;這解釋了類型推斷失敗。編譯器需要知道類型參數,以便爲方法組轉換選擇正確的Foo過載。

+0

@Jacob請參閱編輯的問題。 – phoog 2012-04-19 21:05:53

+0

我會爲我的問題添加IFooService接口;它是一種與'Func '兼容的方法。 – Jacob 2012-04-19 21:07:45

+0

@Jacob編譯器無法推斷方法組轉換的類型,因爲它需要知道類型才能選擇正確的「Foo」重載。這裏當然只有一個重載,但解決一般問題的需要解釋了這種情況下類型推斷的失敗。 – phoog 2012-04-19 21:12:55

0

爲什麼不直接調用該方法? 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); 
    } 
} 

那麼你理論上應該是能夠做到這一點:

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

我想將請求對象作爲參數傳遞的原因是,可以將請求序列化並記錄下來。我可以這樣做,直接調用方法並傳遞請求對象,但我想避免這種冗餘。 – Jacob 2012-04-19 20:58:23

+0

您總是可以將'Func'變成'Expression ',然後檢查表達式的序列化。 – Tejs 2012-04-19 20:59:49

+0

是的,這是一個選項,但我寧願避免使用反射的性能打擊。 – Jacob 2012-04-19 21:00:50

相關問題