Может ли лямбда-выражение 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)

подобное.