1

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... Some code to explain what I mean ...

a = (b > 1) ? b : c;

or even a = (a > 1) ? a : b;

So of course here it's no big deal but if a was to be replaced by an method call, (maybe a yield return there) or whatever, I would then have to call it twice...

Only thing I see is stocking it in a variable which would then be as the code above...

Any better idea?

Edit for better comprehension : Let's say I'm searching a value in a xml file, with null checks (?. ?[]) etc so something like

string store_no = myXmlNode.SelectSingleNode("aChildNode")?.SelectSingleNode("myNode")?.Attributes?["store_no]?.Value;

So here I store this in a variable so that I can test its value later. If I want to check a specific store_no !I would have to do something like

store_no = (store_no=="STORE23")? store_no : "unknown";

...Yeah not sure if it is quite explicit enough with this example but the idea is here; I may not want to store data in a variable (huge data block for example) is there a way to get the same result?

Phoque
  • 217
  • 3
  • 14
  • 8
    What's being repeated? I don't see any repetition here. – Carcigenicate May 25 '18 at 13:06
  • You wrote about including a method call, and that it required repetition. Can you show an example of what you mean by that? – Daxtron2 May 25 '18 at 13:07
  • 6
    Why getting the method's result assigned to a variable is a bad thing? If you don't want to repeat the method call that's the way to do it – Steve May 25 '18 at 13:07
  • Are you trying to avoid something like this `int a = (ReturnsANumber() > 5) ? ReturnsANumber() : 5;`? – Daxtron2 May 25 '18 at 13:10
  • The tested value gets repeated (here a), as I said it could also be replaced by a method call I mean to assign a value depending on if result (so yes here it is the (= ?:) – Phoque May 25 '18 at 13:10
  • 1
    I dont even have an idea what "better idea" you expect. If you want to assign a new value to `a` this must already be a variable(or property) and not an expression. – Tim Schmelter May 25 '18 at 13:10
  • 2
    I think you misunderstand what DRY actually means. – DavidG May 25 '18 at 13:15
  • 2
    The only thing what comes to my mind is, don't use the conditional operator but: `if(a <= 1) a = b;` – Tim Schmelter May 25 '18 at 13:16
  • I might be missing something here - but what would be wrong with a simple method - _"int SetValue(int a, int b) { return (a > 1) ? a : b; }"_ (or appropriate types for a & b) - that way if "a" is replaced by a method call - it is only called once as the parameter to the method call. – PaulF May 25 '18 at 13:17
  • There is no solution, you want the outcome of something.. but only if it meets a certain criteria, however to test the criteria you need to calculate the outcome first. If this is a question about performing an operation as resource friendly as possible you need to examine the operations logic itself – Robbert Draaisma May 25 '18 at 21:39

1 Answers1

8

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Loved it! Lots of info and you clearly got my point (which was not that easy it seems). About DRY, that's how I've been told about it i'll go read more about it. Also, while I talked about DRY, what I meant was actually readability/code factorisation. – Phoque May 28 '18 at 09:10