C # przekazywanie funkcji jako argumentu

141

Napisałem funkcję w C #, która wykonuje różniczkowanie numeryczne. To wygląda tak:

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

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

Chciałbym móc przekazać dowolną funkcję, na przykład:

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

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

Myślę, że jest to możliwe w przypadku delegatów (może?), Ale nie jestem pewien, jak ich używać.

Każda pomoc byłaby bardzo mile widziana.

Popiół
źródło

Odpowiedzi:

146

Używanie Func, jak wspomniano powyżej, działa, ale są też delegaci, którzy wykonują to samo zadanie, a także definiują intencję w nazewnictwie:

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
źródło
5
W wersji 3.5 i nowszych Func <> i delegatów są wymienne, co oznacza, że ​​można również używać anonimowych delegatów i lambd (które są cukrem syntaktycznym dla anonimowych delegatów). Więc tak naprawdę nie ma znaczenia, czy określisz parametr jako Func <double, double>, czy delegat, który przyjmuje double i zwraca double. Jedyną prawdziwą zaletą, jaką daje delegat nazwany, jest możliwość dodawania komentarzy xml-doc; nazwy opisowe są tak samo łatwe do zaimplementowania, jak nazwa parametru zamiast typu.
KeithS
3
Twierdzę, że prototyp metody nadal sprawia, że ​​kod jest bardziej czytelny niż Func <x, y> - to nie tylko nazewnictwo sprawia, że ​​kod jest czytelny i, jak mówisz, nie przeszkadza ci w przekazywaniu lambd do kodu.
Ian Johnson
Jeśli nazewnictwo typu delegata jest tak ważne dla przejrzystości kodu, myślę, że w większości przypadków skłaniałbym się ku interfejsowi i implementacjom.
quentin-starin
@qstarin to nie tylko nazewnictwo delegata, ale nazewnictwo argumentów, które musi przyjąć metoda, zwłaszcza jeśli są to tylko typy natywne. Masz rację, głównie używam interfejsów nad delegatami
Ian Johnson
Jeśli tworzę jedną z nich jako wspólną funkcję, która zapewnia wspólną funkcjonalność (z niezbyt powszechnym typem powrotu), wszystko działa, dopóki nie spróbuję jej użyć z void. Czy naprawdę muszę utworzyć zduplikowaną funkcję za pomocą Action zamiast Func, czy jest lepszy sposób?
Dan Chase,
175

Istnieje kilka typów ogólnych w .Net (v2 i nowsze), które bardzo ułatwiają przekazywanie funkcji jako delegaci.

Dla funkcji z typami zwracanymi istnieje Func <>, a dla funkcji bez typów zwracanych jest Action <>.

Można zadeklarować, że zarówno Func, jak i Action przyjmują od 0 do 4 parametrów. Na przykład Func <double, int> przyjmuje jeden double jako parametr i zwraca int. Akcja <double, double, double> przyjmuje trzy podwojenia jako parametry i nic nie zwraca (void).

Więc możesz zadeklarować swoją funkcję Diff, aby wziąć Func:

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

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

A potem nazywasz to tak, po prostu nadając mu nazwę funkcji, która pasuje do podpisu twojej Func lub Action:

double result = Diff(myValue, Function);

Możesz nawet napisać funkcję w linii ze składnią lambda:

double result = Diff(myValue, d => Math.Sqrt(d * 3.14));
quentin-starin
źródło
29
W .NET 4 oba Funci Actionzostały zaktualizowane, aby umożliwić maksymalnie 16 parametrów.
Joel Mueller
5
Naprawdę fajną rzeczą do zrobienia byłoby zwrócenie Func<double, double>pierwszej pochodnej funkcji wejściowej, oczywiście obliczonej numerycznie. return x => (f(x + h) - f(x)) / h;Możesz nawet napisać przeciążenie, które zwróciło tę npochodną funkcji wejściowej.
Ani
15
public static T Runner<T>(Func<T> funcToRun)
{
    //Do stuff before running function as normal
    return funcToRun();
}

Stosowanie:

var ReturnValue = Runner(() => GetUser(99));
kravits88
źródło
Jestem ciekaw, dlaczego parametr typu generycznego?
kdbanman
2
Otrzymuję ten błąd: argumenty typu dla metody „Runner <T> (Func <T>)” nie mogą być wywnioskowane na podstawie użycia. Spróbuj jawnie określić argumenty typu.
DermFrench
Jaka jest Twoja sygnatura metody „funcToRun”?
kravits88