В C# я пытаюсь написать код, где я создал бы делегата Func, который сам по себе универсален. Например, следующий (неуниверсальный) делегат возвращает произвольную строку:
Func<string> getString = () => "Hello!";
Я, с другой стороны, хочу создать дженерик, который действует так же к общим методам. Например, если я хочу, чтобы универсальный Func возвратил значение по умолчанию (T) для типа T. Я предположил бы, что пишу код следующим образом:
Func<T><T> getDefaultObject = <T>() => default(T);
Затем я использовал бы его как
getDefaultObject<string>()
который возвратил бы пустой указатель и если я должен был записать getDefaultObject<int>()
возвратился бы 0.
Этим вопросом не является просто академическое осуществление. Я нашел многочисленные места, где я, возможно, использовал это, но я не могу разобраться в синтаксисе. Действительно ли это возможно? Есть ли какие-либо библиотеки, которые обеспечивают этот вид функциональности?
Хотя можно найти практические обходные пути, как у Стивена Клири
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
.
Ну, вы не можете ничего перегрузить, основываясь только на возвращаемом значении, поэтому сюда входят переменные.
Однако вы можете избавиться от этого лямбда-выражения и написать настоящую функцию:
T getDefaultObject<T>() { return default(T); }
, а затем вызвать ее точно так, как хотите:
int i=getDefaultObject<int>(); // i=0
string s=getDefaultObject<string>(); // s=null
Это невозможно, поскольку экземпляр делегата в C # не может иметь общие параметры. Самое близкое, что вы можете сделать, - это передать объект типа как обычный параметр и использовать отражение. : (
Во многих случаях приведение к динамическому помогает устранить боль отражения, но динамический не помогает при создании новых экземпляров, таких как ваш пример.
Вы не можете этого сделать, потому что параметры универсального типа должны быть известны во время выполнения. Вы должны использовать класс активатора:
Object o = Activator.CreateInstance (typeof (StringBuilder));
, который будет делать именно то, что вы хотите. Вы можете записать это так:
public T Default<T>()
{
return (T)Activator.CreateInstance(typeof(T));
}
Править
Решение Блинди лучше.