[英]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如何使用MiscUtil
的PartialComparer
将0
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
关键字。
(表达式A ) unless
(布尔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.