How-to: short-circuiting inverted ternary operator implemented in, e.g. C#? Does it matter?

感情迁移 提交于 2019-12-05 18:49:44
ShuggyCoUk

personally I'd avoid the short circuit from operators and just let the methods chain it:

public static int CompareChain<T>(this int previous, T a, T b)
{
    if (previous != 0)
        return previous;
    return Comparer<T>.Default.Compare(a,b);
}

use like so:

int a = 0, b = 2;
string x = "foo", y = "bar";
return a.Compare(b).CompareChain(x,y);

can be inlined by the JIT so it can perform just as well as short circuiting built into the language without messing about with more complexity.

In response to your asking whether the above 'structure' can apply to more than just comparisons then yes it can, by making the choice of whether to continue or not explict and controllable by the user. This is inherently more complex but, the operation is more flexible so this is unavoidable.

public static T ElseIf<T>(
    this T previous, 
    Func<T,bool> isOK
    Func<T> candidate)
{
    if (previous != null && isOK(previous))
        return previous;
    return candidate();
}

then use like so

Connection bestConnection = server1.GetConnection()
    .ElseIf(IsOk, server2.GetConnection)
    .ElseIf(IsOk, server3.GetConnection)
    .ElseIf(IsOk, () => null);

This is maximum flexibility in that you can alter the IsOk check at any stage and are entirely lazy. For situations where the is OK check is the same in every case you can simplify like so and entirely avoid extensions methods.

public static T ElseIf<T>(        
    Func<T,bool> isOK
    IEnumerable<Func<T>[] candidates)
{
   foreach (var candidate in candidates)
   { 
        var t = candidate();
        if (isOK(t))
            return t;
   }
   throw new ArgumentException("none were acceptable");
}

You could do this with linq but this way gives a nice error message and allows this

public static T ElseIf<T>(        
    Func<T,bool> isOK
    params Func<T>[] candidates)
{
    return ElseIf<T>(isOK, (IEnumerable<Func<T>>)candidates);
}

style which leads to nice readable code like so:

var bestConnection = ElseIf(IsOk,
    server1.GetConnection,
    server2.GetConnection,
    server3.GetConnection);

If you want to allow a default value then:

public static T ElseIfOrDefault<T>(        
    Func<T,bool> isOK
    IEnumerable<Func<T>>[] candidates)
{
   foreach (var candidate in candidates)
   { 
        var t = candidate();
        if (isOK(t))
            return t;
   }
   return default(T);
}

Obviously all the above can very easily be written using lambdas so your specific example would be:

var bestConnection = ElseIfOrDefault(
    c => c != null && !(c.IsBusy || c.IsFull),
    server1.GetConnection,
    server2.GetConnection,
    server3.GetConnection);
Eric Lippert

You've got lots of good answers to this question already, and I am late to this particular party. However I think it is worthwhile to note that your proposal is a special case of a more generally useful operation which I dearly wish C# had, namely, the ability to in an expression context, give a name to a temporary computation.

In fact C# has this operator but only in query comprehensions. I wish we had been able to add this as an operator in C# 3:

public static int Compare(Person p1, Person p2) =>
  let ages = Compare(p1.Age, p2.Age) in
    ages != 0 ? 
      ages : 
      let names = Compare(p1.Name, p2.Name) in
      names != 0 ? 
        names : 
        Compare(p1.Salary, p2.Salary);

"Let expressions" are one of those expressions that are so useful, and found in so few languages, and I genuinely do not understand why language designers do not add it immediately in version one.

If C# had this feature then your proposed:

A() unless B() : C()

is simply

let a = A() in B() ? C() : a

which is hardly more difficult to understand, and bonus, you get to use a in expressions B() and C() if you like.

Let expressions can be emulated in any language that has lambdas; of course let x = y in z is simply (x=>z)(y), but there is no concise way to write that in C# either because C# requires a conversion to a delegate type on every lambda.

Incidentally, in Roslyn we do not represent temporaries as let-expressions, though we could. Rather, we go even one level below that and have a representation for "sequence of operations that might produce values, one of which will become the value of this expression". "let x = y in z" is simply the sequence "allocate x, x = y, z, deallocate x" where the third element is the value. And in the original pre-roslyn C# compiler we had internal operators "left" and "right", which were binary operators that took two expressions and produced either the left or right side, so we could generate ((allocate x) right ((x = y) right z)) left (deallocate x).

My point here is: we often get requests for bespoke language features with unusual punctuation, but it would in general have been better to implement the basic building blocks that you could build these operators out of in a natural way.

To place one proposed implementation away from a very verbose question, let's run with the unless keyword.

(expression A) unless (boolean B) <magical "in which case" operator> (expression C)

... would be all there is to it.

Boolean expression B would have access to the evaluation of expression A through the keyword value. Expression C could have the unless keyword in its expression, allowing for simple, linear chaining.

Candidates for the <magical "in which case" operator>:

  • :
  • |
  • ?:
  • otherwise keyword

Usage of any symbols tend to diminish readability for the average developer. Even the ?? operator is not used widely. I, myself, do prefer to develop verbose code, but that I can easily read one year from now.

So a candidate for your :

expression A unless boolean B in which case expression C.

would be

expression A unless boolean B sothen expression C.

Although many people like me would still use:

if (B) {expression C;}
else {expression A;}

This comes in when you are developing a software with a big team, with different backgrounds, each one on the team master of one language, and just user of others.

maxwellb

More @ShuggyCoUk: Ah, I see that this might work for more than just comparisons? I haven't used C# 3 and extension methods, but I suppose you can declare, for my previous example, below, a

public delegate bool Validation<T>( T toTest );
public static T Validate<T>( this T leftside, Validation<T> validator )
{
    return validator(leftside) ? leftside : null;
}

Followed by, per Skeet:

Validation<Connection> v = ( Connection c ) => ( c != null && !( c.IsBusy || c. IsFull ) );
Connection bestConnection =
    server1.GetConnection().Validate( v ) ??
    server2.GetConnection().Validate( v ) ??
    server3.GetConnection().Validate( v ) ?? null;

Is this how that would work in C#? Comments appreciated. Thank you.


In response to ShuggyCoUk:

So this is an extension method in C# 3, then? Also, the result here is an int, not an arbitrary expression. Useful for overloading yet another comparison method. Suppose I wanted an expression for choosing the best connection. Ideally, I want something to simplify the following:

Connection temp;
Connection bestConnection =
    ( temp = server1.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull) ? temp
    :   ( temp = server2.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull ) ? temp
        :   ( temp = server3.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull ) ? temp
            : null;

Ok, so one could have a method

bool IsOk( Connection c )
{
    return ( c != null && !(c.IsBusy || c.IsFull) );
}

Which would produce:

Connection temp;
Connection bestConnection =
    ( temp = server1.GetConnection() ) && IsOk( temp ) ? temp
    :   ( temp = server2.GetConnection() ) && IsOk( temp ) ? temp
        :   ( temp = server3.GetConnection() ) && IsOk( temp ) ? temp
            : null;

But how would method chaining for comparisons work, here? I am pondering something which looks like:

Connection bestConnection =
    server1.GetConnection() unless !IsOk(value) otherwise
    server2.GetConnection() unless !IsOk(value) otherwise
    server3.GetConnection() unless !IsOk(value) otherwise null;

I think that there are so far, hoops to jump through, if I want the result of a conditional to be an expression or result of a method which was in the original conditional.

I assume that the object returned by such methods will be expensive to produce, or will change the next time the method is called.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!