С# 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 лучше.