操作方法:在 C# 等中实现的短路倒三元运算符? 有关系吗?
假设您使用三元运算符、空合并运算符或嵌套 if-else 语句来选择对对象的赋值。 现在假设在条件语句中,您要评估昂贵或易失的操作,要求您将结果放入临时变量中,捕获其状态,以便可以对其进行比较,然后可能进行赋值。
考虑一下,诸如 C# 之类的语言如何实现新的逻辑运算符来处理这种情况? 应该是? C# 中是否存在处理这种情况的现有方法? 其他语言?
例如,当我们假设我们正在寻找直接比较时,一些减少三元或空合并运算符冗长的情况已经被克服。 请参阅使用 Null Coalescing 运算符的独特方法,特别是围绕如何扩展运算符的使用以支持 String.IsNullOrEmpty(string) 的讨论。 请注意 Jon Skeet 如何使用 MiscUtil
,将 0
重新格式化为 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;
null 合并运算符更具可读性,因为它有两侧,而不是三个。 布尔条件子句被分成一个方法,在本例中,如果表达式必须继续,则返回 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 除非 布尔值 B 在这种情况下表达式 C
我认为这更像是一个短路倒置三元运算符,而不是一个重载的比较运算符
- 这种逻辑有用吗? 目前,空合并为我们提供了一种使用条件表达式
(value == null)
来实现此目的的方法。 - 您还想测试哪些其他表达式? 我们听说过
(String.IsNullOrEmpty(value))
。 - 用语言、运算符、关键字来表达这一点的最佳方式是什么?
Suppose you are using the ternary operator, or the null coalescing operator, or nested if-else statements to choose assignment to an object. Now suppose that within the conditional statement, you have the evaluation of an expensive or volatile operation, requiring that you put the result into a temporary variable, capturing its state, so that it can be compared, and then potentially assigned.
How would a language, such as C#, for consideration, implement a new logic operator to handle this case? Should it? Are there existing ways to handle this case in C#? Other languages?
Some cases of reducing the verbosity of a ternary or null coalescing operator have been overcome, when we assume that we are looking for direct comparisons, for example. See Unique ways to use the Null Coalescing operator, in particular the discussion around how one can extend the usage of the operator to support String.IsNullOrEmpty(string)
. Note how Jon Skeet is using the PartialComparer
from MiscUtil
, to reformat 0
s to null
s,
Why is this possibly necessary? Well, take a look at how we write a comparison method for complex objects without any shortcuts (examples from the cited discussions):
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 writes a new comparison to fallback the equality case. This allows the expression to extend by writing a new specific method which returns null, allowing us to use the null coalescing operator:
return PartialComparer.Compare(p1.Age, p2.Age)
?? PartialComparer.Compare(p1.Name, p2.Name)
?? PartialComparer.Compare(p1.Salary, p2.Salary)
?? 0;
The null coalescing operator is more readable because it has two sides, not three. The boolean condition clause is separated into a method, in this case returning null
if the expression must be continued.
What would the above expression look like if we could more easily put the condition in-line? Take the expression from PartialComparer.Compare
which returns null
, and place it in a new ternary expression which allows us to use the evaluation of the left-side expression, with an implicit temporary variable value
:
return Compare( p1.Age, p2.Age ) unless value == 0
: Compare( p1.Name, p2.Name ) unless value == 0
: Compare( p1.Salary, p2.Salary );
The basic "flow" of an expression would be:
expression A unless boolean B in which case expression C
Rather than being an overloaded comparison operator, I suppose this is more like a short-circuiting inverted ternary operator.
- Would this type of logic be useful? Currently the null coalescing provides us a way to do this with the conditional expression
(value == null)
. - What other expressions would you want to test against? We've heard of
(String.IsNullOrEmpty(value))
. - What would be the best way to express this in the language, in terms of operators, keywords?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
就我个人而言,我会避免运算符的短路,而只是让方法将其链接起来:
像这样使用:
可以由 JIT 内联,这样它就可以像语言中内置的短路一样执行,而不会弄乱更多的复杂性。
回答您的问题,上述“结构”是否不仅仅适用于比较,那么是的,可以通过选择是否继续或不由用户明确和控制来实现。 这本质上更复杂,但是操作更灵活,所以这是不可避免的。
然后像这样使用
这是最大的灵活性,因为您可以在任何阶段更改 IsOk 检查并且完全是懒惰的。 对于正常检查在每种情况下都相同的情况,您可以像这样简化并完全避免扩展方法。
你可以使用 linq 来做到这一点,但这种方式会给出一个很好的错误消息,并允许这种
风格,从而产生很好的可读代码,如下所示:
如果你想允许默认值,那么:
显然,以上所有内容都可以很容易地使用 lambda 编写,因此你的具体示例是:
personally I'd avoid the short circuit from operators and just let the methods chain it:
use like so:
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.
then use like so
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.
You could do this with linq but this way gives a nice error message and allows this
style which leads to nice readable code like so:
If you want to allow a default value then:
Obviously all the above can very easily be written using lambdas so your specific example would be:
对于这个问题,你已经有了很多好的答案,而我参加这个特别的聚会却迟到了。 不过,我认为值得注意的是,您的建议是一种更普遍有用的操作的特例,我非常希望 C# 具有这种操作,即能够在表达式上下文中为临时计算命名。
事实上,C# 有这个运算符,但仅在查询推导式中。 我希望我们能够将其添加为 C# 3 中的运算符:
“Let 表达式”是非常有用的表达式之一,并且在极少数语言中找到 ,我真的不明白为什么语言设计者不立即在第一版中添加它。
如果 C# 有这个功能,那么你的建议
是
:这很容易理解,而且额外的好处是,你可以在表达式
B()
和中使用
如果你愿意的话。a
C()Let 表达式可以用任何具有 lambda 表达式的语言进行模拟; 当然,
let x = y in z
就是简单的(x=>z)(y)
,但是在 C# 中也没有简洁的方法来编写它,因为 C# 需要每个 lambda 上到委托类型的转换。顺便说一句,在 Roslyn 中,我们不将临时变量表示为 let 表达式,尽管我们可以。 相反,我们甚至比它低一级,并拥有“可能产生值的操作序列,其中一个将成为该表达式的值”的表示。 “let x = y in z”只是序列“分配 x,x = y,z,释放 x”,其中第三个元素是值。 在最初的 roslyn 之前的 C# 编译器中,我们有内部运算符“left”和“right”,它们是二元运算符,采用两个表达式并生成左侧或右侧,因此我们可以生成
((allocate x)右 ((x = y) 右 z)) 左(释放 x)
。我的观点是:我们经常收到带有不寻常标点符号的定制语言功能的请求,但一般来说,实现可以以自然方式构建这些运算符的基本构建块会更好。
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:
"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:
is simply
which is hardly more difficult to understand, and bonus, you get to use
a
in expressionsB()
andC()
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.
为了使建议的实现远离非常冗长的问题,让我们使用
unless
关键字来运行。(表达式A)
unless
(布尔B) <神奇的“在哪种情况下”运算符> (表达式C)...就是它的全部内容。
布尔表达式B可以通过关键字
value
访问表达式A的求值。 表达式C 可以在其表达式中包含unless
关键字,从而允许简单的线性链接。<神奇的“在哪种情况下”运算符的候选者>:
:
|
?:
otherwise
关键字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 theunless
keyword in its expression, allowing for simple, linear chaining.Candidates for the <magical "in which case" operator>:
:
|
?:
otherwise
keyword使用任何符号都会降低普通开发人员的可读性。 甚至
??
运算符也没有被广泛使用。 我本人确实更喜欢开发冗长的代码,但一年后我可以轻松阅读。因此,您的候选人:
将是
表达式 A 除非布尔值 B sothen 表达式 C.
虽然很多像我这样的人仍然会使用:
当你与一个大团队一起开发一个软件时,这个团队有不同的背景,团队中的每个人都掌握一种语言,这就会出现,并且只是其他人的用户。
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 :
would be
expression A unless boolean B sothen expression C.
Although many people like me would still use:
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.
更多 @ShuggyCoUk:啊,我发现这不仅仅可以用于比较? 我没有使用过 C# 3 和扩展方法,但我想您可以在下面声明我之前的示例,
每个 Skeet:
这在 C# 中是这样工作的吗? 评论赞赏。 谢谢。
回应 ShuggyCoUk:
那么这是 C# 3 中的扩展方法吗? 另外,这里的结果是一个 int,而不是任意表达式。 对于重载另一种比较方法很有用。 假设我想要一个用于选择最佳连接的表达式。 理想情况下,我想要一些东西来简化以下内容:
好的,所以人们可以有一种方法
,它会产生:
但是这里的比较方法链接如何工作? 我正在思考一些看起来像这样的事情:
我认为到目前为止,如果我希望条件的结果是原始条件中的方法的表达式或结果,那么到目前为止,还需要跨越一些障碍。
我假设此类方法返回的对象的生成成本很高,或者会在下次调用该方法时发生更改。
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
Followed by, per Skeet:
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:
Ok, so one could have a method
Which would produce:
But how would method chaining for comparisons work, here? I am pondering something which looks like:
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.