繁体   English   中英

操作方法:在C#中实现短路倒置三元运算符? 有关系吗?

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

假设您正在使用三元运算符,空合并运算符或嵌套的if-else语句来选择对一个对象的赋值。 现在假设在条件语句中,您需要对昂贵或易失的操作进行评估,要求将结果放入一个临时变量中,捕获其状态,以便可以对其进行比较,然后进行潜在赋值。

考虑使用的语言(例如C#)如何实现新的逻辑运算符来处理这种情况? 应该是? 是否存在使用C#处理这种情况的现有方法? 其他语言?

例如,当我们假设我们正在寻找直接比较时,已经克服了一些降低三元或空合并运算符的冗长性的情况。 请参阅使用Null合并运算符的独特方法 ,特别是有关如何扩展运算符的使用范围以支持String.IsNullOrEmpty(string) 请注意, Jon Skeet如何使用MiscUtilPartialComparer0 s重新格式化为null

为什么这可能有必要? 好吧,来看看我们如何为没有任何捷径的复杂对象编写比较方法(引用的讨论中的示例):

public static int Compare( Person p1, Person p2 )
{
    return ( (result = Compare( p1.Age, p2.Age )) != 0 ) ? result
        : ( (result = Compare( p1.Name, p2.Name )) != 0 ) ? result
        : Compare( p1.Salary, p2.Salary );
}

乔恩·斯基特(Jon Skeet)编写了一个新的比较,以回退平等案例。 这允许通过编写新的返回null的特定方法来扩展表达式,从而允许我们使用null合并运算符:

return PartialComparer.Compare(p1.Age, p2.Age)
    ?? PartialComparer.Compare(p1.Name, p2.Name)
    ?? PartialComparer.Compare(p1.Salary, p2.Salary)
    ?? 0;

空合并运算符更具可读性,因为它有两个方面,而不是三个方面。 布尔条件子句分为一个方法,在这种情况下,如果必须继续表达式,则返回null

如果我们可以更轻松地将条件置于行内,则上面的表达式会是什么样? PartialComparer.Compare中获取返回null的表达式, PartialComparer.Compare其放置在新的三元表达式中,该表达式允许我们使用左侧表达式的求值,并带有一个隐式临时变量value

return Compare( p1.Age, p2.Age ) unless value == 0
     : Compare( p1.Name, p2.Name ) unless value == 0
     : Compare( p1.Salary, p2.Salary );

表达式的基本“流”为:

表达式 A除非布尔值 B ,否则表达式 C

我想这不是重载比较运算符,而是更像是短路的倒三元运算符

  • 这种逻辑有用吗? 当前,空合并为我们提供了一种使用条件表达式(value == null)执行此操作的方法。
  • 您还想测试哪些其他表达式? 我们听说过(String.IsNullOrEmpty(value))
  • 就运算符,关键字而言,用语言表达此内容的最佳方式是什么?

我个人会避免操作员的短路,而只是让方法将其链接起来:

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

像这样使用:

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

可以由JIT内联,因此它可以执行和内置于语言中的短路一样的功能,而不会增加复杂性。

响应您的询问,上述“结构”是否可以不仅适用于比较,还可以,它可以通过选择是否继续由用户进行显式控制来实现。 这从本质上讲更加复杂,但是操作更加灵活,因此这是不可避免的。

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

然后像这样使用

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

这是最大的灵活性,因为您可以在任何阶段更改IsOk检查,并且完全懒惰。 对于在每种情况下OK校验都相同的情况,您可以像这样简化并完全避免使用扩展方法。

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");
}

您可以使用linq做到这一点,但是这种方式会给出一个不错的错误消息并允许

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

样式会导致类似如下的可读代码:

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

如果要允许默认值,则:

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);
}

显然,以上所有内容都可以使用lambda轻松编写,因此您的具体示例为:

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

您已经对这个问题有了很多很好的答案,而我对这个特定聚会迟到了。 但是,我认为值得一提的是,您的建议是一个更普遍有用的操作的特例,我非常希望C#具有这样的功能,即能够在表达式上下文中为临时计算命名。

实际上,C#具有此运算符,但仅用于查询理解 我希望我们能够在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表达式”是非常有用的表达式之一,并且使用的语言很少 ,而且我真的不明白为什么语言设计者不会在第一版中立即添加它。

如果C#具有此功能,则您建议:

A() unless B() : C()

很简单

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

很难理解,而且,如果愿意,您可以在表达式B()C()使用a

可以使用具有lambda的任何语言来模拟let表达式; 当然, let x = y in z只是(x=>z)(y) ,但是也没有简洁的方法可以在C#中编写它,因为C#要求每个lambda都转换为委托类型。

顺便提一句,在罗斯林,尽管可以,但我们不将临时对象表示为let表达式。 相反,我们甚至将其下移一个级别,并表示“可能产生值的操作序列,其中之一将成为该表达式的值”。 “让z中的x = y”只是序列“分配x,x = y,z,取消分配x”,其中第三个元素是值。 在原始的roslyn之前的C#编译器中,我们有内部运算符“ left”和“ right”,它们是二进制运算符,采用两个表达式并产生左侧或右侧,因此我们可以生成((allocate x) right ((x = y) right z)) left (deallocate x)

我的意思是:我们经常收到要求使用特殊标点符号的定制语言功能的请求,但是总的来说,实现基本的构建块会更好,您可以自然地构建这些运算符。

为了使一个拟议的实现远离一个非常冗长的问题,让我们使用unless关键字。

(表达式Aunless (布尔B )<神奇的“在这种情况下”运算符>(表达式C

...就是所有的一切。

布尔表达式B可以通过关键字value访问表达式A的求value 表达式C的表达式中可以包含unless关键字,从而可以进行简单的线性链接。

<魔术“在这种情况下”运算符>的候选者:

  • :
  • |
  • ?:
  • otherwise关键字

任何符号的使用都倾向于降低普通开发人员的可读性。 甚至?? 运算符没有被广泛使用。 我本人确实喜欢开发冗长的代码,但是从现在开始我可以轻松阅读一年。

因此,您的候选人:

表达式 A,除非布尔值为 B,在这种情况下为表达式 C。

将会

表达式 A,除非布尔 B如此,则表达式 C。

尽管像我这样的许多人仍然会使用:

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

当您与一个拥有不同背景的大团队一起开发软件时,就会出现这种情况。每个人都是一种语言的团队主管,而另一种语言只是用户。

更多@ ShuggyCoUk :啊,我认为这可能不仅限于比较? 我没有使用C#3和扩展方法,但是我想您可以在下面的上一个示例中声明一个

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

随后,根据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;

这是在C#中如何工作吗? 评论表示赞赏。 谢谢。


回应ShuggyCoUk

那么这是C#3中的扩展方法吗? 同样,这里的结果是一个int,而不是一个任意表达式。 对于重载还有另一种比较方法很有用。 假设我要选择最佳连接的表达式。 理想情况下,我希望可以简化以下内容:

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;

好的,所以可以有一种方法

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

会产生:

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

但是,用于比较的方法链接在这里如何工作? 我正在考虑以下内容:

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

我想,如果我希望条件的结果是原始条件中的表达式或方法的结果,那么到目前为止,还有很多事情要解决。

我认为由此类方法返回的对象生产起来会很昂贵,或者在下次调用该方法时会改变。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM