C# Expression Blocks - Let's make C# more expressive #8411
Replies: 3 comments 14 replies
-
Duplicate of #3086. |
Beta Was this translation helpful? Give feedback.
-
As noted in the other discussion, this is already legal C# syntax, but the |
Beta Was this translation helpful? Give feedback.
-
Fluent method chaining with expression blocks is a novel idea (and not included in the actual expression block proposal, I believe), but it feels like an orthogonal feature since you can't normally just plop an expression in the middle of a method chain. The syntax also feels dangerously close to object initialization, e.g. the following is valid C# today: var obj = new MyObject()
{
value = "foo" // If I add semicolon here, does this turn into an expression block?
}; For fluent expression chaining, I believe the better route would be to add basic expression blocks from the other proposal and then orthogonally add the pipeline operator feature (C# 1, C# 2, JS, C++). Unfortunately, other languages have proven that pipeline operators are a major source of bikeshedding, so this might be too hopeful. |
Beta Was this translation helpful? Give feedback.
-
I propose a new feature for C#, for which I would like your opinions about. I propose Expression Blocks.
Expression Blocks would improve the way we use the fluent pattern, simplify the initialization of auto properties, allow us to use statements in places where only expressions can be used, and more. It uses familiar syntax, and is fully compile time — it is just syntactic sugar.
What are expression blocks
They are a block of C# code that can produce a value for an expression or process an expression value.
Produce a value
An expression block that produces a value would be similar to the block of a property getter. An expression would be a C# block inside brackets
{ }
, that havereturn
statements.Process a value
An expression block that processes values is a block of C# inside brackets
{ }
that has avalue
parameter and can return a value. This block comes after the expression value, similar to thenew() { }
initializer blocks.return
statements here are optional. When the block execution reaches the end, it implicitly returnsvalue
(which might have been changed in the block), unless an explicitreturn
is called before.For conditionally processing a value
It is useful for when we want to process/mutate and/or use an alternative value. Notice that there is no
return
in the end of the block, because it is not necessary — it implicitly returnsvalue
.For mixing with statements
It is useful for when we just need to further process/mutate a value without breaking a call chain (for example, in the fluent pattern).
Concept
The concept of an expression block is just that it is an anonymous local method:
var a = { return 10; };
, it is just an anonymous parameterless method that returns a value of the expected type.var a = b { return value * 2; };
, it is just an anonymous method that receives a parameter (value
) and returns a value of the same type.Like in local methods, expression blocks have their own scope and have access to the outside scope. Unlike local methods, they are "anonymous" methods, written in-place, that cannot be reused.
Because expression blocks return a value, they can be followed by another expression block or by operators, like any other expression. For example, all these would be valid:
var a = b { return value * 2; } { return value + 5; };
var a = b { return value * 2; }.ToString();
var a = b { return value * 2; } { return value + 5; }.ToString();
What does this feature solve
Alternative to complex
? :
chains in expressionsThe use of the ternary conditional
? :
operator gets messy when there is more than one branch. Expressions likea = x > 10 ? c : x < 0 ? d : e;
would be simplified to a more readableif
block inside an expression block.Reuse expression result
A value in an expression can be reused in the same expression. This is useful when a sub expression is expensive to evaluate and must not be evaluated more than once if we need to reuse its value inside the expression.
Fluent pattern
Expression blocks would make it easier to produce and/or process an expression value without breaking a call chain. This would be useful with the fluent pattern.
Enumerables
Expression blocks should support the
yield
keyword. This allows for more complex processing of enumerables, that would not be as practical if using LINQ. This might need some more thought when we take implicitreturn
statements into consideration, though.Scope isolation
With expression blocks, you can make an entire method just a structure of scopes.
This would be useful in scenarios where you want to divide a method into very small tasks that produce a value, but you would benefit if local variables would not interfere with each other. This is already possible with local methods, though.
More expressive code
Expression blocks would allow us to use statements in places where only expressions would be allowed. For example:
The example above also demonstrates how it would work with the
new
initializer blocks. We would always need to include an initializer block before we use an expression block, when instantiating objects withnew
.Some additional considerations
The
value
keywordBecause the
value
keyword is used in property setters, we might be unable to use expression blocks in property setters. To solve this, some other keyword could be used, instead ofvalue
. My suggestion ofvalue
is just to make the proposal more clear, but I suggest the use of other keyword for expression blocks, to prevent conflict with property setters.Async code inside expression blocks
The use of
await
inside an expression block would have the same effect as it has when we now use it in an expression (likevar a = await DoSomethingAsync();
). However, I think I don't have enough expertise with async/await to have a clear big picture on all edge cases.Beta Was this translation helpful? Give feedback.
All reactions