Как определить имена параметров для функций высшего порядка в C # - PullRequest
3 голосов
/ 29 октября 2010

В C # можно создавать функции более высокого порядка, т.е.функции g принимая функции в качестве аргументов.Скажем, я хочу создать такую ​​функцию, которая имеет функцию f и возвращает другую функцию, расширяющую ее функциональность. Как определить имена аргументов для возвращенного расширенного метода? Мотивация заключается в том, что я работаю с методами более высокого порядка в целом, некоторые из которых создают новые методы ... и их может быть сложно использовать какк ним не прикреплены имена параметров и т. д.

Пример, иллюстрирующий, как g и f соответственно могут быть определены в C #:

Я определяю метод Extend, который может расширять методыпринимая T в качестве аргумента и возвращая S. static class M { public static Func< T, S> Extend(Func< T, S> functionToWrap) { return (someT) => { ... var result = functionToWrap(someT); ... return result; }; } }

Затем мы можем расширить метод для нашего класса, не меняя метод. class Calc2 { public Func< int, int> Calc; public Calc2() { Calc = M.Extend< int, int>(CalcPriv); } private int CalcPriv(int positiveNumber) { if(positiveNumber < 0) throw new Exception(...); Console.WriteLine("calc " + i); return i++; } }

Увы, имя аргумента positiveNumber больше не доступно, поскольку единственной доступной информацией является Func<int, int> Calc.Вот когда я использую расширенный метод, набрав new Calc2().Calc(-1), я не получаю помощи от IDE, что на самом деле мой аргумент неверен.

Было бы хорошо, если бы мы могли определить delegate и привести его кэто, однако, это невозможно.

Есть предложения?

Ответы [ 3 ]

2 голосов
/ 29 октября 2010

Если вам нужен только фиксированный тип делегата с именованными параметрами, вы можете просто определить свой собственный тип делегата:

Func просто определен так:

public delegate TResult Func<in T, out TResult>(T arg)

Таким образом, вы можете определить свой собственный тип делегата с желаемым параметром.

Но в вашем примере вы хотите сохранить переданный тип делегата, поэтому здесь это не работает. Теоретически вы можете определить свою функцию следующим образом:

public static T Extend(T functionToWrap)
{
}

К сожалению, нет хороших общих ограничений, которые ограничивают тип ввода делегатом с правильной подписью (или даже просто делегатами). Но без этих ограничений реализация станет настолько уродливой, и вы потеряете столько статической безопасности типов, что IMO не стоит этого.

Один обходной путь использует:

new MyFunc(Extend(f))

где MyFunc определяет нужные имена параметров.

Или вы можете сделать следующее:

public static T ConvertDelegate<T>(Delegate d)
{
    if (!(typeof(T).IsSubclassOf(typeof(Delegate))))
        throw new ArgumentException("T is no Delegate");
    if (d == null)
        throw new ArgumentNullException();
    MulticastDelegate md = d as MulticastDelegate;
    Delegate[] invList = null;
    int invCount = 1;
    if (md != null)
        invList = md.GetInvocationList();
    if (invList != null)
        invCount = invList.Length;
    if (invCount == 1)
    {
        return (T)(object)Delegate.CreateDelegate(typeof(T), d.Target, d.Method);
    }
    else
    {
        for (int i = 0; i < invList.Length; i++)
        {
            invList[i] = (Delegate)(object)ConvertDelegate<T>(invList[i]);
            }
            return (T)(object)MulticastDelegate.Combine(invList);
        }
    }

public static TDelegate Extend<TDelegate,TArg,TResult>(Func<TArg,TResult> functionToWrap)
where TDelegate:class
    {       
        Func<TArg,TResult> wrappedFunc= DoTheWrapping(functionToWrap);
        return ConvertDelegate<TDelegate>(wrappedFunc);
    }

Кстати, функцию ConvertDelegate можно использовать для получения Co / Contravariance для делегатов даже до .net 4.

1 голос
/ 29 октября 2010

Можно привести к делегату с именованными параметрами, динамически привязав вновь созданный делегат к базовому методу делегата Func:

public delegate double CalcFunc(double value);

static class M
{
    public static Func<T, S> Extend<T,S>(Func<T, S> functionToWrap)
    {
      return (someT) => functionToWrap(someT);
    }
}

class Program
{
    private static double Calc(double input)
    {
        return 2*input;
    }

    [STAThread]
    static void Main()
    {
        Func<double, double> extended = M.Extend<double, double>(Calc);

        CalcFunc casted = (CalcFunc)Delegate.CreateDelegate(typeof(CalcFunc), extended.Target, extended.Method);
        Console.WriteLine(casted(2) + " == 4");
        Console.WriteLine("I didn't crash!");
        Console.ReadKey();
    }
}

Одно слово предупреждения: это не сработаетлюбая проверка во время компиляции.Если подписи не совпадают точно, вы получите ошибку привязки во время выполнения (исключая специальную поддержку контравариантности в .NET 4).

0 голосов
/ 29 октября 2010

В этом красота анонимных делегатов;они анонимны.Func является делегатом метода, который принимает int и возвращает int.То, что на самом деле делает функция, и, следовательно, имена параметров, не имеет значения.

Единственный способ, которым это будет работать, - это если бы Calc имел именованный тип делегата, определенный с подписью, идентичной CalcPriv.Все будет работать как написано, включая анонимное расширение, но у вас будет именованный параметр для Calc.

Другой способ передать информацию - это xml-doc Calc с тегом ///<summary></summary>, описывающимпараметр Calc был рассчитан на прием.

Наконец, вы могли бы извлечь из Func<T,TResult> создать класс TakesPositiveInteger<T,TResult>.Это немного далеко, но если вы хотите поговорить о самодокументирующемся коде ...

...