Warum Func <T, bool> anstelle von Prädikat <T>?

210

Dies ist nur eine Neugierfrage, auf die ich mich gefragt habe, ob jemand eine gute Antwort auf Folgendes hatte:

In der .NET Framework-Klassenbibliothek haben wir zum Beispiel diese beiden Methoden:

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
)

Warum verwenden sie Func<TSource, bool>statt Predicate<TSource>? Scheint, als würde das Predicate<TSource>nur von List<T>und verwendet Array<T>, während Func<TSource, bool>es von so ziemlich allen Queryableund EnumerableMethoden und Erweiterungsmethoden verwendet wird ... was ist damit los?

Svish
quelle
21
Ugh ja, die inkonsistente Verwendung dieser macht mich auch verrückt.
George Mauer

Antworten:

170

Während Predicatehat zur gleichen Zeit eingeführt worden , dass List<T>und Array<T>in .NET 2.0, die verschiedenen Funcund Actionkommen Varianten von .net 3.5.

Daher werden diese FuncPrädikate hauptsächlich aus Gründen der Konsistenz in den LINQ-Operatoren verwendet. Ab .net 3.5, über die Verwendung Func<T>und Action<T>die Richtlinie besagt :

Verwenden Sie die neuen LINQ-Typen Func<>und Expression<>nicht benutzerdefinierte Delegaten und Prädikate

Jb Evain
quelle
7
Cool, habe diese Gildenlinien noch nie gesehen =)
Svish
6
Akzeptiert dies als Antwort, da es die meisten Stimmen hat. und weil Jon Skeet viele
Wiederholungen
4
Das ist eine bizarre Richtlinie, wie sie geschrieben wurde. Sicherlich sollte dort "Verwenden Sie die neuen LINQ-Typen" Func <> "und" Action <> "[...]" stehen. Ausdruck <> ist eine ganz andere Sache.
Jon Skeet
3
Nun, eigentlich muss man das in den Kontext der Richtlinie stellen, in der es darum geht, einen LINQ-Anbieter zu schreiben. Für LINQ-Operatoren besteht die Richtlinie darin, Func <> und Expression <> als Parameter für die Erweiterungsmethoden zu verwenden. Ich bin damit einverstanden, dass ich eine separate Richtlinie zur Verwendung von Func and Action
Jb Evain
Ich denke, Skeets Argument ist, dass Ausdruck <> keine Richtlinie ist. Es ist eine C # -Compilerfunktion, um diesen Typ zu erkennen und etwas anderes damit zu tun.
Daniel Earwicker
116

Ich habe mich das schon einmal gefragt. Ich mag den Predicate<T>Delegierten - es ist nett und beschreibend. Sie müssen jedoch die Überlastungen berücksichtigen von Where:

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

Auf diese Weise können Sie auch nach dem Index des Eintrags filtern. Das ist schön und konsequent, während:

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

wäre nicht.

Jon Skeet
quelle
11
Das Prädikat <int, bool> wäre etwas hässlich - ein Prädikat basiert normalerweise (IME der Informatik) auf einem einzelnen Wert. Es könnte natürlich Predicate <Pair <T, int >> sein, aber das ist noch hässlicher :)
Jon Skeet
so wahr ... hehe. Nein, ich denke der Func ist sauberer.
Svish
2
Akzeptierte die andere Antwort aufgrund der Richtlinien. Ich wünschte, ich könnte mehr als eine Antwort markieren! Wäre schön, wenn ich eine Reihe von Antworten als "Sehr bemerkenswert" oder "Auch mit einem guten Punkt, der zusätzlich zur Antwort notiert werden sollte"
markieren könnte
6
Hm, habe gerade gesehen, dass ich diesen ersten Kommentar falsch geschrieben habe: P Mein Vorschlag war natürlich Predicate <T, int> ...
Svish
4
@ JonSkeet Ich weiß, diese Frage ist alt und alles, aber weißt du, was ironisch ist? Der Name des Parameters in der WhereErweiterungsmethode lautet predicate. Heh = P.
Conrad Clark
32

Der eigentliche Grund für die Verwendung Funcanstelle eines bestimmten Delegaten ist sicherlich, dass C # separat deklarierte Delegaten als völlig unterschiedliche Typen behandelt.

Obwohl Func<int, bool>und Predicate<int>beide identische Argumente und Rückgabetypen haben, sind sie nicht zuweisungskompatibel. Wenn also jede Bibliothek ihren eigenen Delegatentyp für jedes Delegatenmuster deklariert, können diese Bibliotheken nur dann zusammenarbeiten, wenn der Benutzer "Bridging" -Delegierte einfügt, um Konvertierungen durchzuführen.

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

Durch die Ermutigung aller zur Verwendung von Func hofft Microsoft, dass dies das Problem inkompatibler Delegatentypen lindert. Alle Delegierten werden gut zusammenspielen, da sie nur anhand ihrer Parameter- / Rückgabetypen abgeglichen werden.

Es werden nicht alle Probleme zu lösen, weil Func(und Action) nicht haben outoder refParameter, aber die sind weniger häufig verwendet.

Update: In den Kommentaren sagt Svish:

Das Umschalten eines Parametertyps von Func auf Predicate und zurück scheint jedoch keinen Unterschied zu machen. Zumindest kompiliert es noch ohne Probleme.

Ja, solange Ihr Programm nur Delegaten Methoden zuweist, wie in der ersten Zeile meiner MainFunktion. Der Compiler generiert stillschweigend Code für ein neues Delegatenobjekt, das an die Methode weitergeleitet wird. In meiner MainFunktion konnte ich x1also ExceptionHandler2ohne Probleme vom Typ wechseln .

In der zweiten Zeile versuche ich jedoch, den ersten Delegierten einem anderen Delegierten zuzuweisen. Obwohl der 2. Delegattyp genau den gleichen Parameter- und Rückgabetyp hat, gibt der Compiler einen Fehler aus CS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2'.

Vielleicht wird dies klarer:

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
}

Meine Methode IsNegativeist eine sehr gute Sache, um die Variablen pund zuzuweisen f, solange ich dies direkt tue. Aber dann kann ich keine dieser Variablen der anderen zuweisen.

Daniel Earwicker
quelle
Das Umschalten eines Parametertyps von Func <T, bool> auf Predicate <T> und zurück scheint jedoch keinen Unterschied zu machen. Zumindest kompiliert es noch ohne Probleme.
Svish
Anscheinend versucht MS, Entwickler davon abzuhalten, zu glauben, dass diese gleich sind. Ich wundere mich warum?
Matt Kocaj
1
Das Umschalten des Parametertyps macht einen Unterschied, wenn der Ausdruck, den Sie an ihn übergeben, separat zum Methodenaufruf definiert wurde, da er dann entweder als Func<T, bool>oder Predicate<T>nicht als vom Compiler abgeleiteter Typ eingegeben wird .
Adam Ralph
30

Der Rat (in 3.5 und höher) ist, das Action<...>und Func<...>- für das "Warum?" - Ein Vorteil ist, dass " Predicate<T>" nur dann sinnvoll ist, wenn Sie wissen, was "Prädikat" bedeutet. Andernfalls müssen Sie sich den Objektbrowser (usw.) ansehen, um den Signatute zu finden.

Umgekehrt Func<T,bool>folgt ein Standardmuster; Ich kann sofort erkennen, dass dies eine Funktion ist, die a annimmt Tund a zurückgibt bool- ich muss keine Terminologie verstehen - einfach meinen Wahrheitstest anwenden.

Für "Prädikat" mag dies in Ordnung gewesen sein, aber ich schätze den Versuch der Standardisierung. Es erlaubt auch viel Parität mit den verwandten Methoden in diesem Bereich.

Marc Gravell
quelle