Ответ 1
Вы хотите, чтобы следующее утверждение было истинным:
Если у меня есть
Func<T>
, я должен использовать его там, где требуетсяAction
.
Для этого потребуется Func<T>
(A), назначаемый Action
или (B), неявно конвертируемый в Action
.
Если мы предположим (A), что потребует T
, которое может быть любым типом, назначаемым void
.
Эрик Липперт отвечает на этот вопрос в своем блоге:
Должен ли "void" считаться супертипом всех возможных типов для целей ковариантных преобразований возвращаемого типа из групп методов для делегирования типов?
Его ответ "Нет", потому что это в конечном счете несовместимо с спецификацией CLI. Спецификация CLI требует, чтобы возвращаемые значения включались в стек, поэтому void
функции не приводят к генерации команды "pop", а те, которые что-то возвращают, генерируют команду "pop". Если бы какой-то способ иметь "действие", которое могло бы содержать функцию void
или функцию, которая возвращала что-то, что не было известно во время компиляции, компилятор не знал, генерировать ли "поп" ".
Далее он говорит следующее:
Если бы спецификация CLI говорила, что возвращаемое значение любой функции передается обратно в "виртуальный регистр", а не вставляется в стек, мы могли бы сделать делегаты, возвращающие пустоты, совместимыми с функциями, которые вернули что-либо. Вы всегда можете просто игнорировать значение в регистре. Но это не то, что указано в CLI, так что это не то, что мы можем сделать.
Другими словами, если бы существовал этот "виртуальный регистр", в котором хранились возвращаемые значения функций (которые, предположительно, не существуют в спецификации CLI), авторы С# и его компилятора могли бы сделать то, что вы хотите, но они не могут, так как они не могут отклоняться от спецификации CLI.
Если мы предположим, что (B), было бы нарушение, как объясняет Эрик Липперт в в этом блоге, Применив пример из своего блога к этому, если было неявное преобразование от Func<T>
до Action
, некоторые программы больше не будут компилироваться, чем использовалось (сбой изменения). Эта программа в настоящее время компилируется, но попробуйте не комментировать неявный оператор преобразования, сродни тому, о чем вы просите, он не компилируется.
public class FutureAction
{
public FutureAction(FutureAction action)
{
}
//public static implicit operator FutureAction(Func<int> f)
//{
// return new FutureAction(null);
//}
public static void OverloadedMethod(Func<FutureAction, FutureAction> a)
{
}
public static void OverloadedMethod(Func<Func<int>, FutureAction> a)
{
}
public static void UserCode()
{
OverloadedMethod(a => new FutureAction(a));
}
}
(Это не совсем то, что они будут делать, очевидно, так как это работает только для Func<int>
, а не Func<T>
, но это иллюстрирует проблему.)
Резюме
Я думаю, что проблема, с которой вы столкнулись, - это артефакт спецификации CLI, который, вероятно, не был в то время, и я предполагаю, что они не хотят вводить изменения, чтобы позволить имплицитное преобразование для него просто работа.