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.

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

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

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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.