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

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

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

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

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

 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; 

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

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

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

表达式的基本“流程”将是:

表达式 A除非boolean B,在这种情况下表达式为 C

我认为这更像是一个短路的倒三元运算符,而不是一个重载的比较运算符。

  • 这种逻辑会有用吗? 目前,null合并为我们提供了一种使用条件表达式(value == null)执行此操作的方法。
  • 您想要测试哪些其他表达式? 我们听说过(String.IsNullOrEmpty(value))
  • 在运营商,关键词方面,用语言表达这一点的最佳方式是什么?

我个人避免操作员的短路,只是让方法链接它:

 public static int CompareChain(this int previous, T a, T b) { if (previous != 0) return previous; return Comparer.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( this T previous, Func isOK Func 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( Func isOK IEnumerable[] 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( Func isOK params Func[] candidates) { return ElseIf(isOK, (IEnumerable>)candidates); } 

样式导致可读的代码如此:

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

如果要允许默认值,则:

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

显然,以上所有内容都可以使用lambdas编写,因此您的具体示例如下:

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

“让表达式”是那些非常有用的表达式之一,并且在如此少的语言中找到,我真的不明白为什么语言设计者不会在第一版中立即添加它。

如果C#具有此function,那么您建议:

 A() unless B() : C() 

很简单

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

这很难理解,如果你愿意,你可以在表达式B()C()使用奖励。

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

顺便说一下,在罗斯林,我们并不把临时代表作为let-expressions,尽管我们可以。 相反,我们甚至比它低一级并且具有“可能产生值的操作序列的表示,其中一个将成为该表达式的值”。 “令x = y in z”只是序列“allocate x,x = y,z,deallocate x”,其中第三个元素是值。 在原始的pre-roslyn C#编译器中,我们有内部运算符“left”和“right”,它们是二元运算符,它采用两个表达式并生成左侧或右侧,因此我们可以生成((allocate x) right ((x = y) right z)) left (deallocate x)

我的观点是:我们经常收到带有不寻常标点符号的定制语言function的请求,但实际上通过更好地实现基本构建块 ,您可以以自然的方式构建这些运算符。

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

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

……就是它的全部。

布尔表达式B可以通过关键字value访问表达式Avalue 。 表达式C可以在其表达式中使用unless关键字,允许简单的线性链接。

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

使用任何符号往往会降低普通开发人员的可读性。 连?? 运算符没有被广泛使用。 我,我自己,更喜欢开发详细的代码,但我可以在一年后轻松阅读。

所以你的候选人:

表达式 A除非boolean B在哪种情况下表达式 C.

将会

表达式 A除非boolean B sothen 表达式 C.

虽然像我这样的很多人仍会使用:

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

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

更多@ ShuggyCoUk :啊,我看到这可能不仅仅是比较吗? 我没有使用C#3和扩展方法,但我想你可以声明,对于我之前的例子,下面是a

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

其次,根据Skeet:

 Validation 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; 

我认为到目前为止,如果我希望条件的结果是原始条件中的方法的表达式或结果,那么就可以跳过。

我假设这些方法返回的对象生成起来很昂贵,或者下次调用方法时会更改。