20

I have the following method, and I want to know if there is anything that can go in place default(void) below because there is a compiler error that says that void is not valid here:

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)  );
}

I realize that I can use the if-else statement instead of the ternary operator (or that I could call a dummy method to return void). Also, the Select extension method does not like lambdas that return void. It seems to say that the type cannot be inferred, but of course if I specify the type like this, either:

defaults.Select<ApplicationConfiguration, void>((item) => { if (item.Key.Invoke(configuration)) item.Value.Invoke(configuration); } );

I was curious from a language design standpoint, why we don't have expressions that can return void or the data type for variables that is void.

Raghu Dodda
  • 1,505
  • 1
  • 21
  • 28
  • 1
    "It is a pity there is no extension method called DoForEach on collections" it takes about 3 lines of code to add an extenssion to IEnumerable to do just that – Remus Rusanu Jan 08 '10 at 17:48
  • Are you sure you don't mean return NULL instead of void? – NotMe Jan 08 '10 at 17:48
  • 1
    @Remus Rusanu: Read this - there's a good reason that wasn't added. In my opinion, it's a shame they did it on List: http://blogs.msdn.com/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx – Reed Copsey Jan 08 '10 at 17:49
  • @Chris: Replacing default(void) with null does not help either. The compiler says there is no implict conversion between null and void – Raghu Dodda Jan 08 '10 at 17:52
  • @Raghu: Select must return something. You can't just use an action in select - you always have to make select return some type T, and void is "nothing", so it doesn't work. – Reed Copsey Jan 08 '10 at 18:00

5 Answers5

18

I refer you to section 7.1 of the specification, which states:

[An expression may be classified as] "nothing". This occurs when the expression is an invocation of a method with a return type of void. An expression classified as nothing is only valid in the context of a statement expression.

[Emphasis added].

That is to say that the only time you may use an expression which is a void-returning method invocation is when the expression makes up an entire statement. Like this:

M();
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    Perfect. +1 for answering the exact question. – Raghu Dodda Jan 08 '10 at 21:08
  • 1
    Also, a `void` expression can be the right-hand side of the `=>` lambda arrow. The entire expression including the arrow does not have a type in itself. And in the case where the `=>` needs to be converted to an expression tree, that cannot be writtens as a block with a single statement in it. For example `Expression tree = () => Console.WriteLine("Hello");` is legal and appears to contain a sub-expression of type `void`. `Expression tree2 = () => { Console.WriteLine("Hello"); };` will not compile because the statement body `{ ... }` is not allowed for expression trees. – Jeppe Stig Nielsen Jul 11 '14 at 08:35
  • 1
    @JeppeStigNielsen: Good point. Of course the right side of a lambda operator is a context in which a *statement expression* is legal. And it has always struck me as odd that it is legal to have "nothing" expressions in an expression tree. Expression trees were designed to represent side-effect-free operations, but a void-returning method is almost certainly there for a side effect. – Eric Lippert Jul 14 '14 at 19:04
  • (1) Is "a void-returning method invocation" both an expression and a statement? (2) https://stackoverflow.com/q/44234246/156458 – Tim May 29 '17 at 04:04
  • @Tim: no. An expression statement ends in a semicolon. An expression does not. – Eric Lippert May 29 '17 at 04:29
  • @Eric: Thanks. At https://stackoverflow.com/q/44234246/156458, i am wondering: (1) Is a call to a method always an expression, whose value is the value returned by the method? (2) is a void expression always an invocation of a method which returns void? – Tim May 29 '17 at 04:33
  • As of C# 7, you can throw in an expression. – Brian May 30 '17 at 13:16
11

This is, in effect, a violation of functional programming rules. This has the same flaw Eric Lippert described about List.ForEach: You're philosphically trying to cause side effects on your collection.

Enumerable.Select is intended to return a new collection - filtering the input. It is not intended to execute code.

That being said, you can work around this by doing:

defaults.Where(item => item.Key.Invoke(configuration)).ToList().ForEach( item => item.Value.Invoke(configuration));

It's just not as clear as doing:

var matches = defaults.Where(item => item.Key.Invoke(configuration));
foreach(var match in matches)
    match.Value.Invoke(configuration);
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
6

Firstly, you should really avoid putting side-effects in standard linq query operators, and secondly this won't actually work since you aren't enumerating the Select query anywhere. If you want to use linq you could do this:

foreach(var item in defaults.Where(i => i.Key.Invoke(configuration)))
{
   item.Value.Invoke(configuration);   
}

Regarding your question, I'm pretty sure there are no possible values of void and you can't return it explicity. In functional languages such as F#, void is replaced with 'unit' i.e. a type with only one possible value - if you wanted you could create your own unit type and return that. In this case you could do something like this:

defaults.Select(item => {
    if(item.Key.Invoke(configuration))
    {
        item.Value.Invoke(configuration);
    }
    return Unit.Value;
}).ToList();

But I really can't recommend doing this.

Lee
  • 142,018
  • 20
  • 234
  • 287
3

From a language standpoint, void means "does not exist", which begs the question: what value would there be in declaring a variable that does not exist?

The problem here is not a language limitation but the fact that you're using a construct (the ternary operator) that demands two rvalues -- where you only have one.

If I may be blunt, I'd say you're avoiding if/else in favor of pointless brevity. Any tricks you come up with to replace default(void) will only serve to confuse other developers, or you, in the future, long after you've stopped bothering with this sort of thing.

Ben M
  • 22,262
  • 3
  • 67
  • 71
  • 1
    I agree with you about pointless brevity. I would not do this in production code; I am just trying to grok lambdas in some toy code I am writing. – Raghu Dodda Jan 08 '10 at 18:00
2

default doesn't work with void; but it works with a type. The Action class produces no result, but the Func<> object always has to return a result. Whatever item.Value.Invoke() returns just return the default of that, as in:

default(object)

or if it's a specific type:

default(SomeType)

Like that.

Brian Mains
  • 50,520
  • 35
  • 148
  • 257
  • Thanks. I understand that, but my question is if there is something else that can go in place of default(void). – Raghu Dodda Jan 08 '10 at 17:45
  • yes default() that is what I'm saying. You have to return a default of a type, not void. You can't do void with a FUnc<>. – Brian Mains Jan 08 '10 at 20:49