2012-02-23 2 views
11

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

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 является родовым Convertion из строки в какой-то объект (думаю десериализации). GeneralizedFunction использует две специализации преобразования: один для типа A и один для типа B. Я знаю, что могу сделать это несколькими способами (например, вводя параметр для типа объекта), но я ищу объяснения того, возможно ли это или невозможно сделать это с помощью дженериков/лямбда. Если Transform специализируется до того, как он будет передан как параметр для Обобщенной функции, это невозможно. Тогда возникает вопрос, почему эта возможность ограничена.

+0

Что это вы хотите сделать именно? так как вы не хотите предоставлять GeneralizedFunction информацию о типе в отношении функции Transform, почему бы не принять func снова, взяв строку и возвращая объект (каждый из которых знает, что все есть *) – Polity

+0

Дело в том, что «Сделайте что-то с A и B "скрывает проблематичную часть. Всегда ли А и В будут конкретными типами? Тогда вам не нужны дженерики. Являются ли они произвольными (возможно, с ограничениями) типами? Тогда «Обобщенная функция» должна быть общей в них. – AakashM

+0

A и B - конкретные типы, но Transform - это общая функция. – Max

ответ

1

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

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

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

+0

Я попробовал. Проблема в том, что вы пытаетесь обратиться к обоим типам A и B с T здесь. Это было мое намерение удалить из объявления функции. – Max

+0

Тогда вам нужно будет заменить A и B на T. – Matthias

+0

Мне нужно выполнить конкретные операции над объектами внутри GeneralizedFunction для простоты. Я не писал никаких подробностей в своем примере кода, но все, что написано, необходимо. – Max

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

То, что вы просите сделать, невозможно только с использованием дженериков. Компилятор должен сгенерировать две типизированные версии вашей функции 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); 
} 

компилятор точно знает, что ему нужно, чтобы произвести в этом случае.

+0

Да, я знаю, что могу передать два экземпляра, но я надеялся, что можно передать функцию GENERIC (отсюда заголовок моего вопроса) и создать две специализации этой общей функции внутри генерализованной функции. – Max

+0

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

+0

Если я посмотрю код IL, сгенерированный для функции Transform, кажется, что есть только одна его версия. Даже когда я применяю его к двум классам объектов. Таким образом, кажется, что специализация универсальной функции выполняется во время выполнения. Не так ли? – Max

1

Кажется, что ответ «нет».

Когда вы звоните 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 
} 

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

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

EDIT

Анализ, почему это не представляется возможным:

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

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

Чтобы передать общую функцию как обобщенная функция, кажется, компилятор должен был бы быть в состоянии пройти группу метод или лямбда-выражения методу без преобразования, поэтому 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)); 
} 
+0

Один вариант использования - десериализация. Строка представляет собой представление объекта, а Transform создает экземпляр этого объекта из его строкового представления. – Max

+0

@Max. Но какой прецедент используется для передачи 'Transform' в' GeneralizedFunction', а не просто для вызова его напрямую? Во всяком случае, не будет генерировать 'T Transform (string)' просто метод удобства вокруг 'object Deserialize (type, string)' like 'T Transform (string s) {return (T) Deserialize (typeof (T) , s); } ' – phoog

+0

Например, Transform1 может преобразовать строку в объект, Transform2 может преобразовать файл, который строка указывает на объект – Max

4

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

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

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

// ... in some class: 

void Test(IGenericFunc genericFunc) 
{ 
    // for example's 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"); 
} 
Смежные вопросы