Передача общей функции в качестве параметра

Я знаю, что то, что я делаю, может быть сделано по-другому, но мне интересно, как все работает. Ниже приведен упрощенный код, который не компилируется, но он должен показывать мою цель.

private void Execute()
{
    GeneralizedFunction("1", "2", i => Transform(i));
}

void GeneralizedFunction(string aStringA, string aStringB, Func<string, T> aAction)
{
    A result1 = aAction(aStringA);
    B result2 = aAction(aStringB);
    // Do something with A and B here
}

T Transform<T>(string aString)
{
    return default(T);
}

Transform - это общее преобразование из строки в некоторый объект (подумайте о десериализации). GeneralizedFunction использует две специализации преобразования: один для типа A и один для типа B. Я знаю, что могу сделать это несколькими другими способами (скажем, введя параметр для типа объекта), но я ищу для объяснения того, возможно ли это или невозможно сделать это с помощью generics/lambdas. Если Transform специализируется, прежде чем он будет передан как параметр для GeneralizedFunction, тогда это невозможно. Тогда возникает вопрос, почему эта возможность ограничена.

Ответ 1

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

Один из способов его решения - перейти в две версии:

private void Execute()
{
    GeneralizedFunction("1", "2", i => Transform<A>(i), i => Transform<B>(i));
}

void GeneralizedFunction(string aStringA, string aStringB, Func<string, A> aAction,  Func<string, B> bAction)
{
    A result1 = aAction(aStringA);
    B result2 = bAction(aStringB);
}

Компилятор точно знает, что ему нужно генерировать в этом случае.

Ответ 2

Этот ответ не объясняет причину почему, просто , как, чтобы обойти ограничение.

Вместо передачи фактической функции вы можете передать объект с такой функцией:

interface IGenericFunc
{
    TResult Call<TArg,TResult>(TArg arg);
}

// ... in some class:

void Test(IGenericFunc genericFunc)
{
    // for example sake only:
    int x = genericFunc.Call<String, int>("string");
    object y = genericFunc.Call<double, object>(2.3);
}

Для вашего конкретного варианта использования его можно упростить:

interface IDeserializerFunc
{
    T Call<T>(string arg);
}

// ... in some class:
void Test(IDeserializerFunc deserializer)
{
    int x = deserializer.Call<int>("3");
    double y = deserializer.Call<double>("3.2");
}

Ответ 3

Попробуйте выполнить следующую подпись:

void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction)

(Обратите внимание, что обобщенная функция должна быть общей, компилятор автоматически угадает параметр типа при вызове метода).

Ответ 4

Кажется, ответ "нет".

Когда вы вызываете Transform напрямую, вам нужно указать параметр типа:

int i = Transform<int>("");

Итак, гипотетически, если бы вы могли передать неполную конструкцию, как вы хотите, вам также нужно будет указать параметры типа:

void GeneralizedFunction(string aStringA, string aStringB, Func<string, T> aAction)
{
    A result1 = aAction<A>(aStringA);
    B result2 = aAction<B>(aStringB);
    // Do something with A and B here
}

Итак, мне кажется, что вы могли бы гипотетически сделать это, если бы у С# был такой синтаксис.

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

ИЗМЕНИТЬ

Анализ того, почему это невозможно:

Когда вы используете выражение lambda в своем коде, оно компилируется в делегат или дерево выражений; в этом случае это делегат. Вы не можете иметь экземпляр "открытого" типа общего типа; другими словами, для создания объекта из общего типа необходимо указать все параметры типа. Другими словами, нет способа иметь экземпляр делегата без предоставления аргументов для всех его параметров типа.

Одна из полезных функций компилятора С# - это неявные преобразования групп методов, где имя метода ( "группа методов" ) может быть неявно преобразовано в тип делегата, представляющий одну из перегрузок этого метода. Точно так же компилятор неявно преобразует лямбда-выражение в тип делегата. В обоих случаях компилятор испускает код для создания экземпляра типа делегата (в этом случае передать его функции). Но экземпляр этого типа делегата по-прежнему должен иметь аргумент типа для каждого из его параметров типа.

Чтобы передать общую функцию как общую функцию, похоже, компилятор должен был бы передать группу методов или лямбда-выражение методу без преобразования, поэтому параметр aAction каким-то образом имел бы тип "группа методов" или "лямбда-выражение". Затем неявное преобразование в тип делегата может произойти на сайтах вызовов A result1 = aAction<A>(aStringA); и B result2 = aAction<B>(aStringB);. Конечно, на данный момент мы хорошо находимся во вселенной противопоказаний и гипотез.

Решение, которое я придумал за обедом, состояло в этом, предполагая функцию Deserialize<T>, которая берет строку, содержащую сериализованные данные, и возвращает объект типа T:

void GeneralizedFunction<T>(string aStringA, string aStringB, Func<T, string> stringGetter)
{
    A result1 = Deserialize<A>(stringGetter(aStringA));
    B result2 = Deserialize<B>(stringGetter(aStringB));
}

void Example(string serializedA, string serializedB, string pathToA, string pathToB, FileInfo a, FileInfo b)
{
    GeneralizedFunction(serializedA, serializedB, s => s);
    GeneralizedFunction(pathToA, pathToB, File.ReadAllText);
    GeneralizedFunction(a, b, fi => File.ReadAllText(fi.FullName));
}

Ответ 5

void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction)
{
    A result1 = aAction(aStringA);
    B result2 = aAction(aStringB);
}

T Transform<T>(string aString)
{
    return default(T);
}