C # -Funktion als Argument übergeben

141

Ich habe eine Funktion in C # geschrieben, die eine numerische Differenzierung vornimmt. Es sieht aus wie das:

public double Diff(double x)
{
    double h = 0.0000001;

    return (Function(x + h) - Function(x)) / h;
}

Ich möchte in der Lage sein, jede Funktion zu übergeben, wie in:

public double Diff(double x, function f)
{
    double h = 0.0000001;

    return (f(x + h) - f(x)) / h;
}

Ich denke, dass dies mit Delegierten möglich ist (vielleicht?), Aber ich bin mir nicht sicher, wie ich sie verwenden soll.

Jede Hilfe wäre sehr dankbar.

Asche
quelle

Antworten:

146

Die Verwendung der oben genannten Funktion funktioniert, es gibt jedoch auch Delegierte, die dieselbe Aufgabe ausführen und auch die Absicht innerhalb der Benennung definieren:

public delegate double MyFunction(double x);

public double Diff(double x, MyFunction f)
{
    double h = 0.0000001;

    return (f(x + h) - f(x)) / h;
}

public double MyFunctionMethod(double x)
{
    // Can add more complicated logic here
    return x + 10;
}

public void Client()
{
    double result = Diff(1.234, x => x * 456.1234);
    double secondResult = Diff(2.345, MyFunctionMethod);
}
Ian Johnson
quelle
5
In Version 3.5 und höher sind Funcs und Delegaten austauschbar. Dies bedeutet, dass auch anonyme Delegierte und Lambdas (die für anonyme Delegierte syntaktischer Zucker sind) verwendet werden können. Es spielt also keine Rolle, ob Sie den Parameter als Func <double, double> oder als Delegat angeben, der ein double nimmt und ein double zurückgibt. Der einzige wirkliche Vorteil, den ein benannter Delegat Ihnen bietet, ist die Möglichkeit, XML-Doc-Kommentare hinzuzufügen. Beschreibende Namen sind genauso einfach zu implementieren wie der Parametername anstelle des Typs.
KeithS
3
Ich würde argumentieren, dass der Methodenprototyp den Code immer noch besser lesbar macht als der Func <x, y> - es ist nicht nur die Benennung, die den Code lesbar macht, und wie Sie sagen, hindert Sie dies nicht daran, Lambdas in den Code zu übergeben.
Ian Johnson
Wenn die Benennung des Delegatentyps für die Klarheit des Codes so wichtig ist, würde ich mich in den meisten Fällen auf eine Schnittstelle und Implementierungen stützen.
Quentin-Starin
@qstarin Es ist nicht nur die Benennung des Delegaten, sondern auch die Benennung der Argumente, die die Methode annehmen muss, insbesondere wenn es sich nur um native Typen handelt. Sie haben Recht, meistens verwende ich Schnittstellen über Delegierten
Ian Johnson
Wenn ich eine dieser Funktionen als allgemeine Funktion mache, die gemeinsame Funktionen bietet (mit einem nicht so üblichen Rückgabetyp), funktioniert alles, bis ich versuche, sie mit void zu verwenden. Muss ich wirklich eine doppelte Funktion mit Action anstelle von Func erstellen oder gibt es einen besseren Weg?
Dan Chase
174

In .Net (Version 2 und höher) gibt es einige generische Typen, die das Weitergeben von Funktionen als Delegierte sehr einfach machen.

Für Funktionen mit Rückgabetypen gibt es Func <> und für Funktionen ohne Rückgabetypen gibt es Action <>.

Sowohl Func als auch Action können so deklariert werden, dass sie 0 bis 4 Parameter annehmen. Zum Beispiel nimmt Func <double, int> ein double als Parameter und gibt ein int zurück. Die Aktion <double, double, double> verwendet drei Doubles als Parameter und gibt nichts zurück (void).

So können Sie Ihre Diff-Funktion für eine Func deklarieren:

public double Diff(double x, Func<double, double> f) {
    double h = 0.0000001;

    return (f(x + h) - f(x)) / h;
}

Und dann nennen Sie es so, indem Sie ihm einfach den Namen der Funktion geben, die zur Signatur Ihrer Funktion oder Aktion passt:

double result = Diff(myValue, Function);

Sie können die Funktion sogar in Übereinstimmung mit der Lambda-Syntax schreiben:

double result = Diff(myValue, d => Math.Sqrt(d * 3.14));
Quentin-Starin
quelle
28
In .NET 4 wurden beide Funcund Actionaktualisiert, um bis zu 16 Parameter zu ermöglichen.
Joel Mueller
5
Eine wirklich coole Sache wäre, eine zurückzugeben Func<double, double>, die die erste Ableitung der Eingabefunktion ist, natürlich numerisch berechnet. return x => (f(x + h) - f(x)) / h;Sie könnten sogar eine Überladung schreiben, die die ndritte Ableitung der Eingabefunktion zurückgibt.
Ani
15
public static T Runner<T>(Func<T> funcToRun)
{
    //Do stuff before running function as normal
    return funcToRun();
}

Verwendung:

var ReturnValue = Runner(() => GetUser(99));
kravits88
quelle
Ich bin gespannt, warum der generische Typparameter?
Kdbanman
2
Ich erhalte die folgende Fehlermeldung: Die Typargumente für die Methode 'Runner <T> (Func <T>)' können nicht aus der Verwendung abgeleitet werden. Versuchen Sie, die Typargumente explizit anzugeben.
DermFrench
Was ist Ihre "funcToRun" -Methodensignatur?
Kravits88