Я новичок в С#, просто вопрос о делегате Func:
public delegate TResult Func<in T,out TResult>(T arg);
Я могу понять необходимость поместить in
ключевом слове перед T, как мы не хотим, чтобы изменить входной источник, но как насчет out
до TResult? не означает ли это, что нам нужно изменить выход, но почему? разве мы иногда не генерируем возвращаемый объект на лету, скажем, у нас есть делегат:
Func<string, bool> nameFilter = str => str[0] == 'S';
поэтому он проверяет строку, чтобы увидеть, является ли ее первый символ 'S', затем возвращает true или false, поэтому мы динамически возвращаем это логическое значение, что здесь делает ключевое слово out? нет ничего, что нужно изменить, чтобы вернуться?
Вам редко нужно беспокоиться о in
и out
ключевых слов в родовых определениях типов. Класс определяется in
/out
параметров общего типа, как правило, "просто работать", когда вы потребляющие его, и я бы держать пари, что большинство дэвов никогда не будут писать такое определение в своем собственном коде.
Чтобы получить полное объяснение, вы должны прочитать Covariance и Contravariance and Variance in Delegates. Остальная часть моего ответа - просто пример иллюстративного кода.
Чтобы упростить объяснение, я собираюсь объяснить, in
и out
отдельно через Action<T>
и Func<TResult>
вместо Func<T,TResult>
.
Все примеры используют следующие два класса:
class BaseClass {}
class DerivedClass : BaseClass {}
out
В этом примере я имитировал Func<out TResult>
, но удалил модификатор out
(ковариация), чтобы продемонстрировать его эффект. Covariance позволяет нам использовать функцию, которая возвращает DerivedType
везде, где ожидается функция, которая возвращает BaseType
.
class CovarianceExamples
{
// This is similar to System.Func<out TResult>(), but with covariance removed
delegate TResult InvariantFunc<TResult>();
void InvariantFuncExample()
{
// Ignore the values of these variables; it the types that are important
InvariantFunc<BaseClass> baseFunc = null;
InvariantFunc<DerivedClass> derivedFunc = null;
baseFunc = baseFunc; // Allowed
baseFunc = derivedFunc; // Not allowed; compile error!
}
void CovariantFuncExample()
{
// Ignore the values of these variables; it the types that are important
Func<BaseClass> baseFunc = null;
Func<DerivedClass> derivedFunc = null;
baseFunc = baseFunc; // Allowed
baseFunc = derivedFunc; // Allowed
}
}
in
Для этого примера я имитировал Action<in T>
, но удалил модификатор in
(contravariance), чтобы продемонстрировать его эффект. Contravariance позволяет нам использовать действие, которое принимает BaseType
везде, где ожидается действие, которое принимает DerivedType
.
class ContravarianceExamples
{
// This is similar to System.Action<in T>(T), but with contravariance removed
delegate void InvariantAction<T>();
void InvariantActionExample()
{
// Ignore the values of these variables; it the types that are important
InvariantAction<BaseClass> baseAction = null;
InvariantAction<DerivedClass> derivedAction = null;
baseAction = baseAction; // Allowed
derivedAction = baseAction; // Not allowed; compile error!
}
void ContravariantActionExample()
{
// Ignore the values of these variables; it the types that are important
Action<BaseClass> baseAction = null;
Action<DerivedClass> derivedAction = null;
baseAction = baseAction; // Allowed
derivedAction = baseAction; // Allowed
}
}