web-dev-qa-db-ja.com

Predicate <T>ではなくFunc <T、bool>を使用する理由

これは単なる好奇心の質問であり、誰かに良い答えがあったかどうか疑問に思っていました:

.NET Frameworkクラスライブラリには、たとえば次の2つのメソッドがあります。

public static IQueryable<TSource> Where<TSource>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, bool>> predicate
)

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate
)

なぜFunc<TSource, bool>の代わりにPredicate<TSource>を使用するのですか? Predicate<TSource>List<T>およびArray<T>でのみ使用され、Func<TSource, bool>はほとんどすべてのQueryableおよびEnumerableメソッドで使用されるようですおよび拡張メソッド...それは何ですか?

206
Svish

PredicateList<T>およびArray<T>と同時に導入されましたが、.net 2.0では、異なるFuncおよびActionバリアントが.net 3.5から導入されました。

したがって、これらのFunc述語は、主にLINQ演算子の一貫性のために使用されます。 .net 3.5以降、Func<T>およびAction<T>の使用について ガイドラインの状態

カスタムデリゲートおよび述語の代わりに、新しいLINQ型Func<>およびExpression<>を使用してください

161
Jb Evain

私はこれを以前に疑問に思いました。私は好き Predicate<T>デリゲート-わかりやすく説明的。ただし、Whereのオーバーロードを考慮する必要があります。

Where<T>(IEnumerable<T>, Func<T, bool>)
Where<T>(IEnumerable<T>, Func<T, int, bool>)

これにより、エントリのインデックスに基づいてフィルタリングすることもできます。それは素敵で一貫していますが、

Where<T>(IEnumerable<T>, Predicate<T>)
Where<T>(IEnumerable<T>, Func<T, int, bool>)

ありません。

111
Jon Skeet

確かに、特定のデリゲートの代わりにFuncを使用する実際の理由は、C#が個別に宣言されたデリゲートをまったく異なる型として扱うことです。

たとえ Func<int, bool>およびPredicate<int>両方とも同じ引数と戻り値の型を持ち、代入互換ではありません。したがって、すべてのライブラリが各デリゲートパターンに対して独自のデリゲートタイプを宣言した場合、ユーザーが変換を実行するために「ブリッジング」デリゲートを挿入しない限り、それらのライブラリは相互運用できません。

    // declare two delegate types, completely identical but different names:
    public delegate void ExceptionHandler1(Exception x);
    public delegate void ExceptionHandler2(Exception x);

    // a method that is compatible with either of them:
    public static void MyExceptionHandler(Exception x)
    {
        Console.WriteLine(x.Message);
    }

    static void Main(string[] args)
    {
        // can assign any method having the right pattern
        ExceptionHandler1 x1 = MyExceptionHandler; 

        // and yet cannot assign a delegate with identical declaration!
        ExceptionHandler2 x2 = x1; // error at compile time
    }

誰もがFuncを使用することを奨励することで、Microsoftはこれが互換性のないデリゲートタイプの問題を軽減することを望んでいます。全員のデリゲートは、パラメーター/戻り値の型に基づいて照合されるだけなので、一緒にうまく再生できます。

Func(およびAction)がoutまたはrefパラメーターを持つことはできないため、すべての問題を解決するわけではありませんが、これらはあまり一般的ではありません。

更新: Svishのコメント:

それでも、パラメータタイプをFuncからPredicateに切り替えたり戻したりしても、違いはないようです。少なくとも問題なくコンパイルできます。

はい、私のMain関数の最初の行のように、プログラムがデリゲートにメソッドのみを割り当てる限り。コンパイラは、メソッドに転送するデリゲートオブジェクトを新規に作成するコードをサイレントに生成します。したがって、私のMain関数では、x1はタイプExceptionHandler2問題を引き起こすことなく。

ただし、2行目では、最初のデリゲートを別のデリゲートに割り当てようとします。 2番目のデリゲート型はまったく同じパラメーターと戻り値型を持っていると考えても、コンパイラはエラーCS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2'

たぶんこれはそれを明確にするでしょう:

public static bool IsNegative(int x)
{
    return x < 0;
}

static void Main(string[] args)
{
    Predicate<int> p = IsNegative;
    Func<int, bool> f = IsNegative;

    p = f; // Not allowed
}

私のメソッドIsNegativeは、直接行う限り、pおよびf変数に割り当てるのに最適です。しかし、それらの変数の一方を他方に割り当てることはできません。

30

アドバイス(3.5以降)では、Action<...>およびFunc<...>-「なぜ?」 -1つの利点は、「Predicate<T> "は、"述語 "の意味を知っている場合にのみ意味があります。そうでない場合は、オブジェクトブラウザ(など)を見て署名を見つける必要があります。

逆にFunc<T,bool>は標準パターンに従います。これはTを取り、boolを返す関数であることをすぐに知ることができます-用語を理解する必要はありません-私の真実のテストを適用してください。

「述語」の場合、これは問題ないかもしれませんが、標準化の試みに感謝します。また、その領域の関連するメソッドと同等の多くのことができます。

27
Marc Gravell