С# Generic Generics (серьезный вопрос)

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

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

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

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

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

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

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

Ответ 1

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

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

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


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

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

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. На данный момент система типа С# не может идти дальше.

Нам нужна была такая нотация, как

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 на polymorphism об этой ноте forall.

Ответ 2

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

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

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

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

int i=getDefaultObject<int>();       // i=0
string s=getDefaultObject<string>(); // s=null

Ответ 3

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

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

Ответ 4

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

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

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

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

Edit

Решение Blindy лучше.