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

次のコンパイル エラーが発生します。

メソッド ... の型引数は、使用法から推測できません。型引数を明示的に指定してみてください。

明らかに、呼び出しを次のように変更することで修正できます。

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

3 に答える 3

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

私たちは2つの事実を決定しなければなりません:何でありAR何ですか?どのような情報を続ける必要がありますか?これnew Bar()はに対応しA、にs=>s.Foo対応しFunc<I, Func<A, R>>ます。

明らかに、それはその最初の事実からであるにA違いないと判断できます。そして明らかに、それはであるに違いないとBar判断できます。これで、に対応することがわかりました。sI(I s)=>s.FooFunc<I, Func<Bar, R>>

ここで問題は、ラムダの本体で オーバーロード解決を実行することによってRそれを推測できるかどうかです。ints.Foo

悲しいことに、答えはノーです。あなたと私はその推論を行うことができますが、コンパイラーはできません。型推論アルゴリズムを設計したとき、この種の「マルチレベル」ラムダ/デリゲート/メソッドグループ推論を追加することを検討しましたが、それがもたらす利益にはコストが高すぎると判断しました。

申し訳ありませんが、ここでは運が悪いです。一般に、複数レベルの機能抽象化を「掘り下げる」必要がある推論は、C#メソッドタイプの推論では行われません。

では、拡張メソッドを使用するときにこれが機能したのはなぜですか?

拡張メソッドには複数レベルの機能抽象化がないためです。拡張メソッドの場合は次のとおりです。

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.FooFunc<Bar, R>i.Foo(Bar)i.Foo(Bar)intRint

メソッドグループを含むこの種の推論は、C#3に追加することを目的としていましたが、私は混乱し、時間内に完了しなかったことに注意してください。そのような推論をC#4に追加することになりました。

この種の推論が成功するには、すべてのパラメータータイプがすでに推論されている必要があることにも注意してください。リターンタイプのみを推測する必要があります。リターンタイプを知るには、オーバーロード解決を実行できる必要があり、オーバーロード解決を実行するには、すべてのパラメータータイプを知る必要があるためです。「ああ、メソッドグループにはメソッドが1つしかないので、オーバーロード解決の実行をスキップして、そのメソッドが自動的に勝つようにする」などのナンセンスは行いません。

于 2012-04-19T21:10:05.227 に答える
0

svc.Foo最後に編集した後、それがメソッドグループであることがわかります。それは型推論の失敗を説明しています。Fooコンパイラは、メソッドグループ変換の正しいオーバーロードを選択できるように、型引数を認識する必要があります。

于 2012-04-19T20:49:51.480 に答える
0

メソッドを直接呼び出さないのはなぜですか? いいえ:

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);
     }
}
于 2012-04-19T20:55:03.657 に答える