Может ли лямбда-выражение C# вернуть void?
у меня есть следующий метод, и Я хочу знать, есть ли что-нибудь, что может пойти на место по умолчанию(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.
5 ответов
Я отсылаю вас к разделу 7.1 спецификации, которая гласит:
[выражение может быть классифицировано как] "ничего." Это происходит, когда выражение-это вызов метод с возвращаемым типом void. выражение, классифицированное как ничто, допустимо только в контексте выражение утверждения.
[Курсив добавлен].
то есть единственный раз, когда вы можете использовать выражение, которое является вызов метода void-returning-это когда выражение составляет весь оператор. Вот так:
M();
это, по сути, нарушение правил функционального программирования. Это имеет тот же недостаток Эрик Липперт описал список.По каждому элементу: вы филосфически пытаетесь вызвать побочные эффекты в вашей коллекции.
перечисли.Select предназначен для возврата новой коллекции-фильтрации входных данных. Он не предназначен для выполнения кода.
как говорится, ты can работа вокруг этого, делая:
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);
С точки зрения языка, void
означает "не существует", что вызывает вопрос: какое значение будет при объявлении переменной, которая не существует?
проблема здесь не в ограничении языка, а в том, что вы используете конструкцию (тернарный оператор), которая требует двух значений-где у вас есть только одно.
Если я могу быть прямолинейным, я бы сказал, что вы избегаете if/else в пользу бессмысленной краткости. Любые трюки, которые вы придумаете, чтобы заменить default(void)
будет только служить, чтобы запутать других разработчиков, или вы, в будущем, долго после того, как вы перестали беспокоиться о такого рода вещи.
во-первых, вы действительно должны избегать размещения побочных эффектов в стандартных операторах запросов 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();
но я действительно не могу рекомендовать это делать.
default не работает с void; но он работает с типом. Класс Action не дает результата, но объект Func всегда должен возвращать результат. Любой предмет.Значение.Invoke () возвращает просто возвращает значение по умолчанию, как в:
по умолчанию(объект)
или если это определенный тип:
по умолчанию(SomeType)
подобное.