2012-04-19 2 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.FooFunc<I, Func<A, R>>에 해당합니다.

확실히 첫 번째 사실에서 ABar이어야한다고 판단 할 수 있습니다. 그리고 분명히 우리는 sI이어야한다고 판단 할 수 있습니다. 이제 (I s)=>s.FooFunc<I, Func<Bar, R>>에 해당합니다.

질문 : 우리는 람다 본문에 s.Foo에 과부하 해결을 수행하여 Rint이라고 추측 할 수 있습니까?

슬프게도 대답은 '아니오'입니다. 당신과 나는 그 추론을 할 수 있지만 컴파일러는 그렇지 않습니다. 타입 추론 알고리즘을 설계 할 때 우리는 이러한 종류의 "다중 레벨"λ/delegate/method 그룹 추론을 추가하는 것을 고려했지만 그것이 제공하는 이익을 얻기에는 너무 높은 비용이라고 결정했습니다.

죄송합니다. 여기서 운이 좋았습니다. 일반적으로, 하나 이상의 수준의 기능 추상화를 "파헤쳐야"하는 추론은 C# 메소드 유형 추론에서 만들어지지 않습니다.

Why did this work when using extension methods, then?

확장 메서드에는 하나 이상의 기능 추상화 수준이 없기 때문에. 확장 방법의 경우는 다음과 같습니다 통화 사이트

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

이제 어떤 정보를 우리가해야합니까와

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

? 이전처럼 ABar 인 것으로 추론합니다. 이제 이 i.FooFunc<Bar, R>으로 매핑된다는 것을 알고 있다는 것을 추론해야합니다. 이는 직접적인 과부하 해결 문제입니다. 우리는 i.Foo(Bar)에 대한 호출이 있었고 과부하 해결이 그 일을하도록했습니다. 오버로드 해상도가 다시 나타나며 i.Foo(Bar)int을 반환하므로 Rint입니다.

이러한 종류의 추론 (방법 그룹 포함)은 C# 3에 추가하기위한 것이었지만 나는 엉망이되었고 시간 내에 완료하지 못했습니다. 우리는 C# 4에 그런 추론을 추가했습니다.

이러한 추론이 성공하려면 모든 매개 변수 유형이 이미으로 추론되어야합니다. 리턴 타입을 알기 위해서는 오버로드 해상도를 수행 할 수 있어야하고 오버로드 해상도를 수행 할 수 있어야하므로 모든 파라미터 유형을 알아야하기 때문에 만 반환 유형을으로 추론해야합니다. "오, 메서드 그룹에는 하나의 메서드 만 있으므로, 과부하 해결을 건너 뛰고 해당 메서드가 자동으로 실행되도록하십시오"와 같은 말도 안합니다.

+0

나는 본다; 그건 의미가 있습니다. – Jacob

0

를 마지막으로 편집 한 후, 우리는 svc.Foo는 방법 그룹임을 알; 타입 추론 실패를 설명합니다. 컴파일러는 형식 인수를 알아야 메서드 그룹 변환에 대해 올바른 Foo 오버로드를 선택할 수 있습니다.

+0

@ 제이콥 편집 된 질문을 참조하십시오. – phoog

+0

제 질문에 IFooService 인터페이스를 추가 할 것입니다; 'Func '와 호환되는 메소드입니다. – Jacob

+0

@Jacob'Foo'의 올바른 오버로드를 선택하기 위해 형식을 알아야하기 때문에 컴파일러는 메서드 그룹 변환을 위해 형식을 유추 할 수 없습니다. 여기에 하나의 과부하가 있지만, 일반적인 문제를 해결할 필요가있는 경우 유형 유추 실패를 설명합니다. – phoog

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

요청 개체를 인수로 전달하려는 이유는 요청을 serialize하고 로깅 할 수 있기 때문입니다. 나는 둘 다 할 수있는, 메서드를 직접 호출하고 요청 개체를 전달하지만이 중복을 피하기 위해 싶습니다. – Jacob

+0

'Func'을'Expression '에 넣고 시리얼 표현식을 검사 할 수 있습니다. – Tejs

+0

예, 옵션입니다.하지만 리플렉션을 사용하면 성능 저하가 발생하지 않도록하는 것이 좋습니다. – Jacob

관련 문제