2012-04-19 55 views
7

Hãy nói rằng tôi có một giao diện dịch vụ trông như thế này:Tại sao không phải suy luận kiểu làm việc trong mã này?

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

Tôi muốn thực hiện một số lo ngại xuyên suốt khi gọi phương pháp trên các dịch vụ như thế này; ví dụ: tôi muốn ghi nhật ký yêu cầu hợp nhất, ghi nhật ký hiệu suất và xử lý lỗi. Biện pháp của tôi là phải có một cơ sở chung "Repository' lớp học với một phương pháp Invoke rằng sẽ chăm sóc của cách gọi phương pháp này và làm những việc khác xung quanh nó lớp cơ sở của tôi trông giống như sau:..

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

này hoạt động tốt Tuy nhiên , toàn bộ mục tiêu của tôi làm cho mã sạch bị cản trở bởi thực tế là suy luận kiểu không hoạt động như mong đợi Ví dụ, nếu tôi viết một phương pháp như thế này:.

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

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

tôi nhận được lỗi biên dịch này:

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

Rõ ràng, tôi có thể sửa chữa nó bằng cách thay đổi cuộc gọi của tôi để:

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

Nhưng tôi muốn tránh điều này. Có cách nào để làm lại mã để tôi có thể tận dụng suy luận kiểu?

Edit:

Tôi cũng nên đề cập đến rằng một cách tiếp cận trước đó đã sử dụng một phương pháp khuyến nông; suy luận kiểu cho công việc này:

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

Trả lời

12

Câu hỏi đó là tiêu đề của câu hỏi của bạn là "tại sao không gõ suy luận làm việc trong mã này" Hãy chỉ đơn giản là mã trong câu hỏi. Kịch bản là ở trái tim của 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); 
    } 
} 

các trang web gọi là

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

Chúng ta phải xác định hai sự kiện: là gì AR? Chúng ta phải tiếp tục thông tin gì? Điều đó new Bar() tương ứng với As=>s.Foo tương ứng với Func<I, Func<A, R>>.

Rõ ràng chúng tôi có thể xác định rằng A phải là Bar từ thực tế đầu tiên đó. Và rõ ràng chúng tôi có thể xác định rằng s phải là I. Bây giờ chúng ta biết rằng (I s)=>s.Foo tương ứng với Func<I, Func<Bar, R>>.

Bây giờ câu hỏi đặt ra là: chúng ta có thể phỏng đoán rằng Rint bằng cách thực hiện độ phân giải quá tải trên s.Foo trong phần thân của lambda?

Đáng buồn thay, câu trả lời là không. Bạn và tôi có thể làm điều đó suy luận, nhưng trình biên dịch thì không. Khi chúng tôi thiết kế thuật toán suy luận kiểu, chúng tôi xem xét thêm kiểu suy luận nhóm "đa cấp" lambda/delegate/phương thức này nhưng quyết định rằng chi phí quá cao cho lợi ích mà nó sẽ mang lại.

Xin lỗi, bạn đã hết may mắn ở đây; nói chung, các suy luận có thể yêu cầu "đào bới" nhiều hơn một mức trừu tượng chức năng không được đưa ra trong suy luận kiểu phương thức C#.

Why did this work when using extension methods, then?

Do phương pháp mở rộng không có nhiều mức trừu tượng chức năng. Trường hợp phương pháp khuyến nông là:

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

với một trang web gọi

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

Bây giờ những thông tin chúng ta có? Chúng tôi suy ra rằng ABar như trước đây. Bây giờ chúng ta phải suy ra những gì R là biết rằng i.Foo bản đồ đến Func<Bar, R>. Đây là một vấn đề phân giải quá tải đơn giản; chúng tôi giả vờ rằng có một cuộc gọi đến i.Foo(Bar) và để cho độ phân giải quá tải thực hiện công việc của mình. Phân giải quá tải quay lại và nói rằng i.Foo(Bar) trả lại int, vì vậy Rint.

Lưu ý rằng kiểu suy luận này - liên quan đến một nhóm phương pháp - được dự định sẽ được thêm vào C# 3 nhưng tôi đã sai lầm và chúng tôi không hoàn thành kịp thời. Chúng tôi đã kết thúc việc thêm suy luận đó vào C# 4.

Cũng lưu ý rằng đối với loại suy luận này để thành công, tất cả các loại tham số phải được suy ra. Chúng ta phải suy ra chỉ kiểu trả về, vì để biết kiểu trả về, chúng ta phải có khả năng thực hiện phân giải quá tải, và để thực hiện phân giải quá tải, chúng ta phải biết tất cả các kiểu tham số. Chúng tôi không làm bất kỳ điều gì vô nghĩa như "oh, nhóm phương pháp chỉ có một phương pháp trong đó, vì vậy hãy bỏ qua việc giải quyết quá tải và chỉ để phương thức đó tự động giành chiến thắng".

+0

Tôi thấy; điều đó có ý nghĩa. – Jacob

0

Sau lần chỉnh sửa cuối cùng, chúng tôi thấy rằng svc.Foo là nhóm phương pháp; giải thích suy luận kiểu suy luận. Trình biên dịch cần biết các đối số kiểu để nó có thể chọn quá tải Foo chính xác cho quá trình chuyển đổi nhóm phương pháp.

+0

@Jacob vui lòng xem câu hỏi đã chỉnh sửa. – phoog

+0

Tôi sẽ thêm giao diện 'IFooService' vào câu hỏi của mình; nó là một phương thức tương thích với 'Func '. – Jacob

+0

@Jacob Trình biên dịch không thể suy ra các loại cho một nhóm chuyển đổi phương pháp vì nó cần phải biết các loại để chọn quá tải chính xác của 'Foo'. Ở đây bạn chỉ có một quá tải, tất nhiên, nhưng cần phải giải quyết vấn đề chung giải thích sự thất bại của suy luận kiểu trong trường hợp này. – phoog

0

Tại sao không chỉ đơn giản gọi trực tiếp phương thức? 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); 
    } 
} 

Sau đó, bạn về mặt lý thuyết sẽ có thể làm điều này:

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

Lý do tại sao tôi muốn chuyển đối tượng yêu cầu làm đối số để yêu cầu có thể được tuần tự hóa và ghi lại. Tôi có thể làm cả hai, gọi phương thức trực tiếp và truyền đối tượng yêu cầu, nhưng tôi muốn tránh sự dư thừa này. – Jacob

+0

Bạn luôn có thể đặt 'Func' thành' Expression 'và sau đó kiểm tra biểu thức để tuần tự hóa. – Tejs

+0

Vâng, đó là một lựa chọn, nhưng tôi muốn tránh hiệu quả của việc sử dụng sự phản chiếu. – Jacob

Các vấn đề liên quan