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.

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

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

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

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.