-1

This question maybe stupid and very basic, but I dont find a way to write it nicely, excepts with extension methods, and I'm not sure there is no way to do what I want.

I know the basic :

result = (a != 0 ? a : b);

But when a is a long operation or result of a query, this become

result = (aReallyLongOperationOrQueryWithIncludes != 0 ? aReallyLongOperationOrQueryWithIncludes : b);

but I dont want to execute twice the aReallyLongOp... OK, you see :)

This is exactly what a coalesce operator like ?? does (predicate is == null), but I cant find one for a custom predicate, excepts by assigning a new variable but I wonder if I can skip this line :

var v = a;
result = (v != 0 ? v : b);

In some cases I use Math.Max(a,b) **, but it only works when the fallback cant be negative and has to be applied on zero or negative values...

Thanks for reading.

** the main usage of this is because EF Core dont returns null when a query return no rows, and the DefaultIfEmpty int returns 0, without ( known by me ) way to ask EF to return null.

An example of aReallyLongOperationOrQueryWithIncludes that needs to return zero if the balance is negative ( sometimes only till a certain amount ) :

return context.Realisations
    .Include(x => x.Artist)
    .Where(x => x.Artist == Artist)
    .Where(x => x.Delivered == true)
    .Sum(x => (x.CreationCost * x.Releases * x.Artist.PM) + x.Artist.UniqueFee - x.Artist.MonthlyCharges);
iguypouf
  • 770
  • 4
  • 15
  • have you tried making it a nullable `int?` – Franz Gleichmann Jul 22 '20 at 18:58
  • 4
    Sorry, I don't understand this question. You've already identified not one, but _two_ alternatives to repeating the expression, either of which work well. As you should already know, there's no built-in _operator_ that would do this. So, what is it that you think you will get in the way of an answer? What type of answer would satisfy you, i.e. would be better than the very good alternatives you've already rejected? – Peter Duniho Jul 22 '20 at 18:59
  • 2
    If you want your result to return null make the return type nullable. For example if you have the query `dbQuery.Select(x => x.Integer).FirstOrDefault()` and you want that FirstOrDefault returns null instead of 0 if there is no result, then just do `dbQuery.Select(x => (int?)x.Integer).FirstOrDefault()`. – ckuri Jul 22 '20 at 19:32
  • Since you want it all "on a single line"; move the helper/temp variable `v`: `int v, result = ((v = a) != 0) ? v : b;` – pfx Jul 22 '20 at 20:03
  • @PeterDuniho I think to know that there is no built-in operator, I ask the question for people who know more than myself... But yeah I know I have the risk that answer is simply no. – iguypouf Jul 22 '20 at 20:14
  • @ckuri Thanks for tip, actually I tried a lot of combinaison on this particular situation but I was trying *dbQuery.Select(x => x.Integer)?* which ( Now I want to say !of curz! ) still returning empty collection, then default non nullable int. Thank you again ! – iguypouf Jul 22 '20 at 20:22
  • @pfx I dont really want "all in one single line", just a way to keep it readable without assign a temporary variable "for nothing", but thank you. – iguypouf Jul 22 '20 at 20:24
  • Note that [this answer](https://stackoverflow.com/a/43360004/2557128) suggests using `result` as the temporary with `result = a; if (result == 0) result = b;` – NetMage Jul 22 '20 at 22:08
  • I also thought to this approach, but when used with `return` you cant – iguypouf Jul 22 '20 at 22:33
  • Please show a [mcve] so we can visualise what `aReallyLongOperationOrQueryWithIncludes ` means. Mainly since your example seems like you are using a variable, but your explanation seems like you are using a function. The two are quite different. – mjwills Jul 22 '20 at 22:45
  • @mjwills : updated, thanks. As you can see I'm not really concerned with "one single line", for the wheres I like to have one "concept" per line, easier to read and comment. – iguypouf Jul 22 '20 at 23:00
  • Yeah, based on that I'd use `Lazy` or `Math.Max` (the latter in this specific case). – mjwills Jul 22 '20 at 23:02
  • Sorry, what you mean by "Lazy" ? – iguypouf Jul 22 '20 at 23:04
  • https://www.google.com/search?q=c%23+what+is+lazy – mjwills Jul 22 '20 at 23:08
  • Another good thing to read, thanks – iguypouf Jul 22 '20 at 23:10

1 Answers1

0

It seems to me in general what you want to do is make this expression more DRY:

f(a) ? a : b

where a takes a long time to compute or is a complicated expression.

The easiest solution is to create an extension method, which is essentially hiding a temporary variable in a parameter:

public static class ObjectExt {
    public static T IfFalse<T>(this T a, Func<T,bool> testFn, T b) => testFn(a) ? a : b;
}

which allows you to use

a.IfFalse(f, b)

(Not particularly liking the name IfFalse.)

Or you could have a similar utility method with three parameters:

public static class PredUtil {
    public static T Iff<T>(Func<T,bool> testFn, T a, T b) => testFn(a) ? a : b;
}

which allows you to use

Iff(f, a, b)

In general, I think a method parameter is the only other way to cache a value besides a temporary variable, but you don't have to declare the method. I won't claim this is particularly readable, but you can use an immediate lambda call instead.

I use a helper class rather than an inline cast:

public static class To {
    public static Func<TResult> Func<TResult>(Func<TResult> func) => func;
    public static Func<T, TResult> Func<T, TResult>(Func<T, TResult> func) => func;
}

(The full class has Func and Action and Expr defined for up to 8 parameters.)

Now you can create a lambda in the expression and immediately call it:

To.Func((int p) => f(p) ? p : b)(a)

Unfortunately you have to provide a type for p as the compiler can't infer it from the call parameter a.

So your example becomes:

result = To.Func((int p) => p != 0 ? p : b)(aReallyLongOperationOrQueryWithIncludes);

Or using a cast:

result = ((Func<int,int>)(p => p != 0 ? p : b))(aReallyLongOperationOrQueryWithIncludes);

UPDATE: With recent versions of C#, you can also use pattern matching to create a temporary variable:

result = (aReallyLongOperationOrQueryWithIncludes is var temp) && temp != 0 ? temp : b;
NetMage
  • 26,163
  • 3
  • 34
  • 55
  • Thanks, interesting reading. I mentionned earlier extension method because I'm aware of and love to create them, but in this project its not allowed by specs ( microservice is a bigger app ). IfFalse as you say is not very fancy, I'm used to use Fallback – iguypouf Jul 22 '20 at 22:42
  • @iguypouf I added another C# 7.x option, pattern matching, an inline way to create a temporary variable. – NetMage Jul 22 '20 at 22:42