Función de paso de C # como argumento

141

He escrito una función en C # que hace una diferenciación numérica. Se parece a esto:

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

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

Me gustaría poder pasar cualquier función, como en:

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

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

Creo que esto es posible con los delegados (¿tal vez?) Pero no estoy seguro de cómo usarlos.

Cualquier ayuda sería muy apreciada.

Ceniza
fuente

Respuestas:

146

El uso de Func como se mencionó anteriormente funciona, pero también hay delegados que hacen la misma tarea y también definen la intención dentro de los nombres:

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
fuente
55
En 3.5 y posteriores, Func <> sy delegados son intercambiables, y eso significa que también se pueden usar delegados anónimos y lambdas (que son azúcar sintáctica para delegados anónimos). Por lo tanto, realmente no importa si especifica el parámetro como Func <double, double> o un delegado que toma un doble y devuelve un doble. La única ventaja real que le brinda un delegado nombrado es la capacidad de agregar comentarios xml-doc; Los nombres descriptivos son tan fáciles de implementar como el nombre del parámetro en lugar del tipo.
KeithS
3
Yo diría que el prototipo del método todavía hace que el código sea más legible que el Func <x, y>: no es solo el nombre lo que hace que el código sea legible y, como usted dice, eso no le impide pasar lambdas al código.
Ian Johnson
Si el nombre del tipo de delegado es tan importante para la claridad del código, creo que en la mayoría de los casos me inclinaría hacia una interfaz e implementaciones.
quentin-starin
@qstarin no es solo el nombre del delegado sino el nombre de los argumentos que debe tomar el método, especialmente si son solo tipos nativos. Tienes razón, principalmente uso interfaces sobre delegados
Ian Johnson
Si estoy haciendo uno de estos como una función común que proporciona una funcionalidad común, (con un tipo de retorno no tan común) todo funciona hasta que trato de usarlo con void. ¿Realmente necesito hacer una función duplicada usando Action en lugar de Func o hay una mejor manera?
Dan Chase
175

Hay un par de tipos genéricos en .Net (v2 y posteriores) que hacen que pasar funciones como delegados sea muy fácil.

Para funciones con tipos de retorno, existe Func <> y para funciones sin tipos de retorno hay Acción <>.

Se puede declarar que Func y Action toman de 0 a 4 parámetros. Por ejemplo, Func <double, int> toma un doble como parámetro y devuelve un int. La acción <doble, doble, doble> toma tres dobles como parámetros y no devuelve nada (nulo).

Entonces puede declarar su función Diff para tomar un Func:

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

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

Y luego lo llamas así, simplemente dándole el nombre de la función que se ajusta a la firma de tu Func o Action:

double result = Diff(myValue, Function);

Incluso puede escribir la función en línea con la sintaxis lambda:

double result = Diff(myValue, d => Math.Sqrt(d * 3.14));
quentin-starin
fuente
29
En .NET 4, ambos Funcy Actionse han actualizado para permitir hasta 16 parámetros.
Joel Mueller
55
Una cosa realmente genial sería devolver una Func<double, double>que sea la primera derivada de la función de entrada, calculada numéricamente, por supuesto. return x => (f(x + h) - f(x)) / h;Incluso podría escribir una sobrecarga que devolviera la nderivada th de la función de entrada.
Ani
15
public static T Runner<T>(Func<T> funcToRun)
{
    //Do stuff before running function as normal
    return funcToRun();
}

Uso:

var ReturnValue = Runner(() => GetUser(99));
kravits88
fuente
Tengo curiosidad, ¿por qué el parámetro de tipo genérico?
kdbanman
2
Recibo este error: los argumentos de tipo para el método 'Runner <T> (Func <T>)' no se pueden inferir del uso. Intente especificar los argumentos de tipo explícitamente.
DermFrench
¿Cuál es su firma de método "funcToRun"?
kravits88