Может ли лямбда-выражение C # когда-либо возвращать void?
Вопрос
У меня есть следующий метод, и Я хочу знать, есть ли что-нибудь, что может заменить default (void) ниже потому что есть ошибка компилятора, которая говорит, что void здесь недопустим:
private void applyDefaultsIfNecessary(ApplicationConfiguration configuration)
{
var defaults = new Dictionary<Predicate<ApplicationConfiguration>, Action<ApplicationConfiguration>>()
{
// { rule, action } - if rule is true, execute action
{ (c) => c.ConnectionString == null , (c) => c.ConnectionString = "foo" },
{ (c) => c.OutputExcelFilePath == null, (c) => c.ConnectionString = "bar" },
{ (c) => c.OutputDirectory == null, (c) => c.OutputDirectory = "baz" }
};
//Nothing to select, but we want to loop throough the dict and invoke action, if rule is true.
//It is a pity there is no extension method called DoForEach on collections.
defaults.Select((item) => item.Key.Invoke(configuration) ? item.Value.Invoke(configuration) : default(void) );
}
Я понимаю, что могу использовать оператор if-else вместо троичного оператора (или что я мог бы вызвать фиктивный метод для возврата void).Кроме того, методу расширения Select не нравятся лямбды, которые возвращают void.Кажется, это говорит о том, что тип не может быть выведен, но, конечно, если я укажу тип подобным образом, либо:
defaults.Select<ApplicationConfiguration, void>((item) => { if (item.Key.Invoke(configuration)) item.Value.Invoke(configuration); } );
Мне было любопытно с точки зрения языкового дизайна, почему у нас нет выражений, которые могут возвращать void, или типа данных для переменных, который является void .
Решение
Я отсылаю вас к разделу 7.1 спецификации, в котором говорится:
[Выражение может быть классифицировано как] "ничего".Это происходит, когда выражение является вызовом метода с возвращаемым типом void. Выражение, классифицированное как nothing, допустимо только в контексте выражения оператора .
[Курсив добавлен].
То есть нужно сказать , что единственный раз, когда вы можете использовать выражение, которое является вызовом метода, возвращающего пустоту, - это когда выражение составляет целую инструкцию.Вот так:
M();
Другие советы
По сути, это нарушение правил функционального программирования.В этом есть тот же недостаток Эрик Липперт описал список.Для каждого:Вы философски пытаетесь вызвать побочные эффекты в вашей коллекции.
Перечислимый.Select предназначен для возврата новой коллекции - фильтрации входных данных.Он не предназначен для выполнения кода.
При этом, как говорится, вы может обойдите это, выполнив:
defaults.Where(item => item.Key.Invoke(configuration)).ToList().ForEach( item => item.Value.Invoke(configuration));
Это просто не так ясно, как делать:
var matches = defaults.Where(item => item.Key.Invoke(configuration));
foreach(var match in matches)
match.Value.Invoke(configuration);
Во-первых, вам действительно следует избегать использования побочных эффектов в стандартных операторах запросов linq, а во-вторых, на самом деле это не сработает, поскольку вы нигде не перечисляете запрос Select .Если вы хотите использовать linq, вы могли бы сделать это:
foreach(var item in defaults.Where(i => i.Key.Invoke(configuration)))
{
item.Value.Invoke(configuration);
}
Что касается вашего вопроса, я почти уверен, что не существует возможных значений void, и вы не можете вернуть его в явном виде.В функциональных языках, таких как F #, void заменяется на 'unit', т. е.тип с только одним возможным значением - при желании вы могли бы создать свой собственный тип единицы измерения и вернуть его.В этом случае вы могли бы сделать что-то вроде этого:
defaults.Select(item => {
if(item.Key.Invoke(configuration))
{
item.Value.Invoke(configuration);
}
return Unit.Value;
}).ToList();
Но я действительно не могу рекомендовать делать это.
С точки зрения языка, void
означает "не существует", что напрашивается на вопрос:какое значение было бы при объявлении переменной, которая не существует?
Проблема здесь заключается не в языковых ограничениях, а в том факте, что вы используете конструкцию (тернарный оператор), которая требует двух rvalues-значений, тогда как у вас есть только одно.
Если позволите быть откровенным, я бы сказал, что вы избегаете if / else в пользу бессмысленной краткости.Любые уловки, которые вы придумаете, чтобы заменить default(void)
это только сбьет с толку других разработчиков или вас самих в будущем, спустя долгое время после того, как вы перестанете заморачиваться подобными вещами.
по умолчанию не работает с void;но это работает с типом.Класс Action не дает никакого результата, но функция<> объект всегда должен возвращать результат.Независимо от item.Value.Invoke() возвращает просто значение по умолчанию для этого, как в:
значение по умолчанию (объект)
или если это определенный тип:
значение по умолчанию (SomeType)
Вот так.