Передача функции делегата с дополнительными параметрами
У меня есть делегат, который выглядит следующим образом:
public delegate bool ApprovalPrompt(ApprovalType type, int receipt, params string[] info);
Я принимаю делегата этого типа в качестве параметра функции, которую я хочу вызвать. Однако в одной конкретной вызывающей функции я хочу передать некоторые дополнительные данные в функцию, которая соответствует этому делегату.
Вот подпись реализующей функции:
private static bool LogApprovalNeeded(FraudFilterUtilities.ApprovalType type, int receipt, params string[] info)
и это называется следующим образом:
PrepareReceipt(LogApprovalNeeded);
Я хотел бы, чтобы это было:
private static bool LogApprovalNeeded(Customer cust, FraudFilterUtilities.ApprovalType type, int receipt, params string[] info)
который в идеале будет использоваться следующим образом:
PrepareReceipt(LogApprovalNeeded(myCustomer))
Как я могу сделать такую вещь? Я бы предпочел не объявлять поле в классе только для хранения Customer
параметр между одной функцией и обратным вызовом...
6 ответов
Вы можете использовать лямбду для "карри" вашей функции:
PrepareReceipt((type, receipt, info) =>
LogApprovalNeeded(myCustomer, type, receipt, info))
Каррирование функции - это формальный термин для хранения ссылки на функцию, но с одним или несколькими "фиксированными" параметрами, что изменяет сигнатуру метода.
Вы также можете использовать лямбду, если для подписи вашей функции не нужны все аргументы, которые предоставляет делегат; Вы можете эффективно отбросить параметры, не передавая все аргументы в лямбду.
Вы можете использовать лямбду, чтобы достичь того, что вам нужно.
PrepareReceipt((type, receipt, info) =>
LogApprovalNeeded(myCustomer, type, receipt, info));
Или измените LogApprovalNeeded
подпись к:
static bool LogApprovalNeeded(ApprovalType type, int receipt,
Customer cust = null, params string[] info)
{
}
Но это может немного запутать, учитывая, что у вас уже есть переменное число параметров, определенных после cust
,
РЕДАКТИРОВАТЬ: Как правильно заметил Servy, изменение подписи не позволит вам вызвать метод, как вы описали. Если вы переместите логику, связанную с Customer
в PrepareReceipt
тем не менее, вам не нужно будет использовать вышеуказанный подход (который в основном генерирует новый анонимный метод и переносит myCustomer
в закрытии.
Если вам нужно универсальное решение для частичного применения делегатов (сокращение параметров), обратите внимание на библиотеку с открытым исходным кодом NReco Commons, она содержит PartialDelegateAdapter, который может сделать это для любого типа делегата:
var logApprovalForCustomer = (new PartialDelegateAdapter(LogApprovalNeeded,
new[] {myCustomer})).GetDelegate<Func<FraudFilterUtilities.ApprovalType,int,string[],bool>>();
в этом примере 1-й параметр фиксируется значением myCustomer. Кроме того, он также пытается согласовать типы аргументов во время выполнения.
Методы Lamba не идеальны: у них нет атрибутов, и они вносят вклад в грязный код.
Если вы хотите избежать такого рода методов, вы можете сделать это альтернативным способом, который похож на JavaScript .bind()
функция.
Эта функция может быть адаптирована в C# следующим образом, используя статический класс с некоторым методом расширения:
//This code requires the Nu-get plugin ValueTuple
using System.Diagnostics;
public static class Extensions
{
[DebuggerHidden, DebuggerStepperBoundary]
public static WaitCallback Bind(this Delegate @delegate, params object[] arguments)
{
return (@delegate, arguments).BoundVoid;
}
[DebuggerHidden, DebuggerStepperBoundary]
public static Func<object, object> BindWithResult(this Delegate @delegate, params object[] arguments)
{
return (@delegate, arguments).BoundFunc;
}
[DebuggerHidden, DebuggerStepperBoundary]
private static void BoundVoid(this object tuple, object argument)
{
tuple.BoundFunc(argument);
}
[DebuggerHidden, DebuggerStepperBoundary]
private static object BoundFunc(this object tuple, object argument)
{
(Delegate @delegate, object[] arguments) = ((Delegate @delegate, object[] arguments))tuple;
if (argument != null)
if (!argument.GetType().IsArray)
argument = new object[] { argument };
object[] extraArguments = argument as object[];
object[] newArgs = extraArguments == null ? arguments : new object[arguments.Length + extraArguments.Length];
if (extraArguments != null)
{
extraArguments.CopyTo(newArgs, 0);
arguments.CopyTo(newArgs, extraArguments.Length);
}
if (extraArguments == null)
return @delegate.DynamicInvoke(newArgs);
object result = null;
Exception e = null;
int argCount = newArgs.Length;
do
{
try
{
if (argCount < newArgs.Length)
{
object[] args = newArgs;
newArgs = new object[argCount];
Array.Copy(args, newArgs, argCount);
}
result = @delegate.DynamicInvoke(newArgs);
e = null;
} catch (TargetParameterCountException e2)
{
e = e2;
argCount--;
}
} while (e != null);
return result;
}
}
Теперь вы можете создать делегат для вашего метода (не лямбда) и назначить ему несколько фиксированных параметров:
MessageBox.Show(new Func<double, double, double>(Math.Pow).BindWithResult(3, 2)(null).ToString()); //This shows you a message box with the operation 3 pow 2
Таким образом, приведенный ниже код будет производить WaitCallback
делегат:
new Func<double, double, double>(Math.Pow).Bind(3, 2)
Принимая во внимание, что приведенный ниже код будет производить Func<object, object>
делегат:
new Func<double, double, double>(Math.Pow).BindWithResult(3, 2)
Вы можете изменить PrepareReceipt
функция для получения дополнительного параметра. Подпись будет выглядеть примерно так public void PrepareReceipt(Customer customer, ApprovalPrompt approvalPrompt)
чтобы сделать это.
Вы не можете передать его этому делегату, поскольку делегат не объявляет аргумент типа Customer. "Простым ответом" будет изменение подписи делегата для принятия нового аргумента.
Тем не менее, это также потребует модификации всех потребителей делегата.