Função de passagem de C # como argumento

141

Eu escrevi uma função em C # que faz uma diferenciação numérica. Se parece com isso:

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

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

Eu gostaria de poder passar em qualquer função, como em:

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

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

Eu acho que isso é possível com os delegados (talvez?), Mas não sei como usá-los.

Qualquer ajuda seria muito apreciada.

Cinza
fonte

Respostas:

146

O uso do Func, como mencionado acima, funciona, mas também existem delegados que executam a mesma tarefa e também definem a intenção na nomeação:

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
fonte
5
No 3.5 e versões posteriores, Funções e delegados são intercambiáveis, e isso significa que delegados e lambdas anônimos (que são açúcar sintático para delegados anônimos) também podem ser usados. Portanto, não importa se você especificar o parâmetro como Func <double, double> ou um delegado que recebe um double e retorna um double. A única vantagem real que um delegado nomeado oferece é a capacidade de adicionar comentários xml-doc; nomes descritivos são tão fáceis de implementar quanto o nome do parâmetro em vez do tipo.
KeithS 01/09/10
3
Eu argumentaria que o protótipo do método ainda torna o código mais legível que o Func <x, y> - não é apenas a nomeação que torna o código legível e, como você diz, não o impede de passar lambdas para o código.
Ian Johnson
Se a nomeação do tipo de delegado é tão importante para a clareza do código, acho que na maioria dos casos eu estaria inclinado a uma interface e implementações.
quentin-starin
@qstarin não é apenas a nomeação do delegado, mas a nomeação dos argumentos que o método deve receber, especialmente se forem apenas tipos nativos. Você está certo, principalmente eu usar interfaces mais de delegados
Ian Johnson
Se eu estiver criando uma delas como uma função comum que fornece funcionalidade comum (com um tipo de retorno não tão comum) tudo funcionará até que eu tente usá-la com o void. Eu realmente preciso criar uma função duplicada usando Action em vez de Func ou existe uma maneira melhor?
Dan Chase
175

Existem alguns tipos genéricos no .Net (v2 e posterior) que facilitam a passagem de funções como delegados.

Para funções com tipos de retorno, existe Func <> e para funções sem tipos de retorno, há Ação <>.

Pode-se declarar que Func e Ação levam de 0 a 4 parâmetros. Por exemplo, Func <double, int> recebe um double como parâmetro e retorna um int. A ação <double, double, double> usa três duplos como parâmetros e não retorna nada (nulo).

Assim, você pode declarar sua função Diff como tendo um Func:

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

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

E então você chama assim, simplesmente fornecendo o nome da função que se encaixa na assinatura do seu Func ou Action:

double result = Diff(myValue, Function);

Você pode até escrever a função alinhada com a sintaxe lambda:

double result = Diff(myValue, d => Math.Sqrt(d * 3.14));
quentin-starin
fonte
29
No .NET 4, ambos Funce Actionforam atualizados para permitir até 16 parâmetros.
Joel Mueller
5
Uma coisa muito legal a se fazer seria retornar um Func<double, double>que é a primeira derivada da função de entrada, calculada numericamente, é claro. return x => (f(x + h) - f(x)) / h;Você pode até escrever uma sobrecarga que retorne a nderivada da função 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
fonte
Estou curioso, por que o parâmetro de tipo genérico?
kdbanman
2
Estou recebendo este erro: Os argumentos de tipo para o método 'Runner <T> (Func <T>)' não podem ser deduzidos do uso. Tente especificar explicitamente os argumentos de tipo.
DermFrench 03/09
Qual é a sua assinatura do método "funcToRun"?
precisa saber é o seguinte