Fonction de transmission C # comme argument

141

J'ai écrit une fonction en C # qui fait une différenciation numérique. Cela ressemble à ceci:

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

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

Je voudrais pouvoir passer dans n'importe quelle fonction, comme dans:

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

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

Je pense que c'est possible avec les délégués (peut-être?) Mais je ne sais pas comment les utiliser.

Toute aide serait grandement appréciée.

Cendre
la source

Réponses:

146

L'utilisation du Func comme mentionné ci-dessus fonctionne mais il existe également des délégués qui effectuent la même tâche et définissent également l'intention dans la dénomination:

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
la source
5
Dans la version 3.5 et les versions ultérieures, les Func <> et les délégués sont interchangeables, ce qui signifie que les délégués anonymes et les lambdas (qui sont du sucre syntaxique pour les délégués anonymes) peuvent également être utilisés. Donc, peu importe si vous spécifiez le paramètre comme Func <double, double> ou comme délégué qui prend un double et retourne un double. Le seul véritable avantage qu'un délégué nommé vous donne est la possibilité d'ajouter des commentaires xml-doc; les noms descriptifs sont tout aussi faciles à implémenter que le nom du paramètre au lieu du type.
KeithS
3
Je dirais que le prototype de méthode rend toujours le code plus lisible que le Func <x, y> - ce n'est pas seulement la dénomination qui rend le code lisible et comme vous le dites, cela ne vous empêche pas de passer des lambdas dans le code.
Ian Johnson
Si la dénomination du type de délégué est si importante pour la clarté du code, je pense que dans la plupart des cas, je pencherais vers une interface et des implémentations.
quentin-star du
@qstarin ce n'est pas seulement la dénomination du délégué mais la dénomination des arguments que la méthode doit prendre, surtout si ce ne sont que des types natifs. Vous avez raison, j'utilise principalement des interfaces sur les délégués
Ian Johnson
Si j'en fais une en tant que fonction commune qui fournit des fonctionnalités communes, (avec un type de retour pas si courant) tout fonctionne jusqu'à ce que j'essaie de l'utiliser avec void. Ai-je vraiment besoin de créer une fonction dupliquée en utilisant Action au lieu de Func ou y a-t-il un meilleur moyen?
Dan Chase
175

Il existe quelques types génériques dans .Net (v2 et versions ultérieures) qui facilitent le passage de fonctions en tant que délégués.

Pour les fonctions avec des types de retour, il existe Func <> et pour les fonctions sans types de retour, il y a Action <>.

Func et Action peuvent être déclarés prendre de 0 à 4 paramètres. Par exemple, Func <double, int> prend un double comme paramètre et renvoie un int. L'action <double, double, double> prend trois doubles comme paramètres et ne renvoie rien (void).

Vous pouvez donc déclarer votre fonction Diff pour prendre un Func:

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

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

Et puis vous l'appelez ainsi, en lui donnant simplement le nom de la fonction qui correspond à la signature de votre Func ou Action:

double result = Diff(myValue, Function);

Vous pouvez même écrire la fonction en ligne avec la syntaxe lambda:

double result = Diff(myValue, d => Math.Sqrt(d * 3.14));
quentin-starin
la source
29
Dans .NET 4, les deux Funcet Actionont été mis à jour pour autoriser jusqu'à 16 paramètres.
Joel Mueller
5
Une chose vraiment cool à faire serait de renvoyer a Func<double, double>qui est la première dérivée de la fonction d'entrée, calculée numériquement bien sûr. return x => (f(x + h) - f(x)) / h;Vous pouvez même écrire une surcharge qui renvoie le nième dérivé de la fonction d'entrée.
Ani
15
public static T Runner<T>(Func<T> funcToRun)
{
    //Do stuff before running function as normal
    return funcToRun();
}

Usage:

var ReturnValue = Runner(() => GetUser(99));
kravits88
la source
Je suis curieux, pourquoi le paramètre de type générique?
kdbanman
2
J'obtiens cette erreur: les arguments de type pour la méthode 'Runner <T> (Func <T>)' ne peuvent pas être déduits de l'utilisation. Essayez de spécifier les arguments de type explicitement.
DermFrench
Quelle est votre signature de méthode "funcToRun"?
kravits88