C # Generic Generics (Серьезный Вопрос) - PullRequest
11 голосов
/ 24 мая 2010

В C # я пытаюсь написать код, в котором я буду создавать делегат Func, который сам по себе является общим. Например, следующий (не универсальный) делегат возвращает произвольную строку:

Func<string> getString = () => "Hello!";

С другой стороны, я хочу создать универсальный метод, который действует аналогично универсальным методам. Например, если я хочу, чтобы универсальный Func возвращал default (T) для типа T. Я хотел бы представить, что я пишу код следующим образом:

Func<T><T> getDefaultObject = <T>() => default(T);

Тогда я бы использовал его как

getDefaultObject<string>(), который вернул бы ноль, и если бы я написал, getDefaultObject<int>() вернул бы 0.

Этот вопрос не просто академическая тренировка. Я нашел множество мест, где я мог бы использовать это, но я не могу понять синтаксис правильно. Это возможно? Существуют ли библиотеки, которые предоставляют такую ​​функциональность?

Ответы [ 4 ]

8 голосов
/ 24 мая 2010

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

Однако вы можете избавиться от этого лямбда-выражения и написать настоящую функцию:

T getDefaultObject<T>() { return default(T); }

и затем вы называете это так, как вы хотите:

int i=getDefaultObject<int>();       // i=0
string s=getDefaultObject<string>(); // s=null
4 голосов
/ 24 мая 2010

Хотя можно найти практические обходные пути как у Стивена Клири

Func<T> CreateGetDefaultObject<T>() { return () => default(T); }

, где вы можете указать родовые значения напрямую, это довольно интересная проблема с теоретической точки зрения, которая не может быть решена текущей системой типов C #.


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

Рассмотрим следующий пример (псевдо-C #):

Tuple<int[], string[]> Test(Func<?> f) {
    return (f(1), f("Hello"));
} 

В предложенной вами системе вызов может выглядеть так:

Test(x => new[] { x }); // Returns ({ 1 }, { "Hello" })

Но вопрос в том, как набрать функцию Test и ее аргумент f? Очевидно, f отображает каждый тип T в массив T[] этого типа. Так может быть?

Tuple<int[], string[]> Test<T>(Func<T, T[]> f) {
    return (f(1), f("Hello"));
} 

Но это не работает . Мы не можем параметризовать Test с помощью любого конкретного T, поскольку f следует применять к всем типам T. На данный момент система типов C # не может идти дальше.

Нам нужна была запись типа

Tuple<int[], string[]> Test(forall T : Func<T, T[]> f) {
    return (f(1), f("Hello"));
} 

В вашем случае вы можете набрать

forall T : Func<T> getDefaultValue = ...

Единственный язык, который я знаю, который поддерживает этот вид обобщения, это Haskell:

test :: (forall t . t -> [t]) -> ([Int], [String])
test f = (f 1, f "hello")

См. Эту запись в Haskellwiki по полиморфизму об этой forall записи.

0 голосов
/ 24 мая 2010

Это невозможно, поскольку делегат экземпляр в C # не может иметь общих параметров.Самое близкое, что вы можете получить, это передать объект типа как обычный параметр и использовать отражение.: (

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

0 голосов
/ 24 мая 2010

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

Object o = Activator.CreateInstance(typeof(StringBuilder));

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

public T Default<T>()
{
  return (T)Activator.CreateInstance(typeof(T));
}

Редактировать

Решение Блинди лучше.

...