C# универсальные дженерики (серьезный вопрос)

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

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

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

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

Затем я использовал бы его как

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

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

11
задан Matthew Flaschen 24 May 2010 в 14:27
поделиться

4 ответа

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

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.

4
ответ дан 3 December 2019 в 09:40
поделиться

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

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

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

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

int i=getDefaultObject<int>();       // i=0
string s=getDefaultObject<string>(); // s=null
8
ответ дан 3 December 2019 в 09:40
поделиться

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

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

0
ответ дан 3 December 2019 в 09:40
поделиться

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

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

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

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

Править

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

-1
ответ дан 3 December 2019 в 09:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: