I think I'm forgetting something evident but I can't seem to find a way to assign a value if it validates a condition remaining as DRY as possible
Let's start by disabusing you of a common misapprehension.
This is a complete misrepresentation of what DRY means. If you have a Customer
object and you have an Address
object and Customer
has fields BillingCity
and BillingPostalCode
and HomeCity
and so on, then your code is not DRY because the same information was represented redundantly in two places. You should redesign your code so that a Customer
has a collection of Address
objects.
Now, it is indeed a good idea to avoid cut-and-pasting duplicate code all over the show, but DRY is about the design of code in the medium to large scale. DRY absolutely does not mean that your code should never use the same variable twice in the same expression!
Now that we've got that out of the way, let's look at your critique of the language.
We are often in situations where we are in an "expression context" -- that is, a long, possibly fluent-style expression, where we want to avoid doing redundant work. For example, we might have:
x = M() > 0 ? M() : 0;
Maybe calling M()
twice is expensive, or maybe it is not idempotent. Whatever. Doesn't matter. Point is, we don't want to call it twice.
It is irritating that we have to drop out of expression context and into statement context:
var m = M();
x = m > 0 ? m : 0;
That is of course legal, but it is a bit vexing. Also, there are some contexts where it's possibly tricky:
N(P() ?? (M() > 0 ? M() : 0));
Now what do we do? There's no obvious way to preserve the semantics without writing it out in longhand, assuming that we only want to call M()
if P()
is null.
var t = default(T);
var p = P();
if (p == null) {
var m = M();
t = m > 0 ? m : 0;
} else {
t = p.Value;
}
N(t);
YUCK. OMG THAT IS SO HORRIBLE.
Other languages solve this problem by introducing let
expressions. What we would really like is to be able to introduce a new local in the middle of an expression. A common syntax is let ID = EXPRESSION in EXPRESSION
and ID
becomes a read-only variable that has a particular meaning but is only in scope in the in
:
N(P() ?? (let m = M() in m > 0 ? m : 0));
Note that C# does support let
expressions but only in a query comprehension. It would be great if it supported it more generally in the language.
There have been many proposals over the years to add let
expressions, or their more general form, sequencing expressions, into C# over the years. See the github roslyn issue tracker for examples. Maybe this will get into C# 8; if you want it, go participate in the forums.
So what can you do in the meanwhile?
It turns out there are let-expressions in C#. let x = y in z
is simply a nice way to write (((Func<X, Z>)(x=>z))(y))
. So you could write:
N(P() ?? (((Func<int, int>)(m => m > 0 ? m : 0))(M())));
but that looks almost as horrid. That's an unreadable mess.
The problem is the lambda. This would be better:
Func<int, int> f = m => m > 0 ? m : 0;
...
N(P() ?? f(M()));
But that's a little bit opaque. How can we improve on this further?
We could make it a local function, but even better, we could make it an extension method and do fluent programming:
public static int NotNegative(this int x) => x > 0 ? x : 0;
...
N( P() ?? M().NotNegative());
Done. This only evaluates M()
once, but super bonus, it is easier to read because now the program text represents the operation being performed on it, rather than the program text being a slew of hard-to-read punctuation.
Little fluent-style extension methods can make your code a lot easier to read. Get in the habit of using them.