Delphi中是否有或将来会有条件运算符?

33

我太久没有涉及Delphi了,我猜是因为在过去几年里我忙于学习Java和PHP。现在,当我回到做一点Delphi工作的时候,我意识到我真的很想念条件运算符,这个运算符在Java和PHP中都支持。

你的Delphi程序有多少地方会出现像这样的代码行呢?

var s : string;
begin
  ...<here the string result is manipulated>...

  if combo.Text='' then
      s := 'null'
    else
      s := QuotedStr(combo.Text);

  result := result + s;
end;

where a simple

result := result + (combo.text='')?'null':quotedStr(combo.text);

这就足够了。我喜欢的是它不仅缩短了代码,而且避免了声明一些帮助s:string变量。

为什么条件运算符不是 Delphi 的一部分,它们会得到支持吗?我注意到在 Delphi 2009 版本中进行了相当多的语言扩展(泛型),那么为什么不添加此功能呢?


3
正确的名称和标记是“条件运算符”。 - Daniel Daranas
3
我认为它可以被称为 "Daniel": http://msdn.microsoft.com/en-us/library/e4213hs1%28VS.71%29.aspx 或者 http://en.wikipedia.org/wiki/Ternary_operation - Russell Troywest
我同意mrPeregrination的观点,但我重新标记了问题,以便所有人都能满意 :) - Peter Perháč
8
条件运算符是一个三元运算符,就像“/”是二元运算符一样 - 你不会称“/”为“二元运算符”,对吗? - anon
1
我认为像泛型这样的东西与三元运算符的根本区别在于,泛型使得原本不可能实现的事情变得可行,而三元运算符只是一个不太有趣的快捷方式。 - Marco van de Voort
显示剩余3条评论
12个回答

45
这样的运算符不是当前Delphi版本的一部分,因为它不是先前版本的一部分,并且需求不足以证明添加它的成本。 (你会发现这个解释适用于你希望在许多产品中拥有的许多功能。)
Delphi提供了一组IfThen函数,位于Math和StrUtils单元中,但它们具有评估其值参数的不幸属性,因此像这样的代码将失败:
Foo := IfThen(Obj = nil, '<none>', Obj.Name);

要真正做到正确,需要编译器的帮助。在Delphi社区中,我感觉大多数人都不喜欢使用问号和冒号的C风格语法。我看过一些提案,建议使用这样的语法:

Foo := if Obj = nil then
         '<none>'
       else
         Obj.Name;

条件运算符的吸引力之一在于它能让你编写简洁的代码,但是 Delphi 的编写风格使得上面的代码不太吸引人,即使将其放在一行上。

它实际上不需要以运算符的形式出现。Delphi Prism 提供了一个编译器魔术函数 Iif ,它只评估其两个值参数中的一个:

Foo := Iif(Obj = nil, '<none>', Obj.Name);

你问为什么 Delphi 2009 添加了许多其他语言功能,却没有添加像这样的功能。我认为这就是原因。已经有很多其他语言变化需要精细处理;开发人员不需要承担更多负担。功能不是免费的。
你问 Delphi 是否会有这样一个功能。我不知道 Embarcadero 的规划会议,而且我必须把我的水晶球送修,所以我不能确定,但我预测如果它曾经有这样一个功能,它将采用 Delphi Prism 的 Iif 函数形式。这个想法出现在Quality Central的讨论的最后,有人提出反对意见,认为作为一个新的保留字,它会破坏与其他人已经定义了相同名称函数的代码的向后兼容性。然而,这不是一个有效的反对意见,因为它不需要是一个保留字。它可以是一个标识符,并且就像 Writeln 和 Exit 一样,即使来自 System 单位的一个特别处理,也可以被重新定义在其他单位中。

7
无论语法效果被归类为“好看”还是“优雅”,这是一些争议的问题,@PA。我发现你的例子是一个难以理解的代码块。我甚至无法在第一次阅读时理解它——我甚至不能一次解析它。空格和括号可能会有所帮助。我尽量避免使用嵌套条件运算符。 - Rob Kennedy
2
@PA 我赞同Rob的观点。如果这使代码更难理解,我不会称其为优雅。我认为你的例子是比较运算符的滥用。虽然在“混淆C代码竞赛”中可能是一种有用的技巧。 - Kenneth Cochran
1
Delphi已经从Java和C ++世界中获得了足够多的复杂功能(例如泛型),足以让我用上七辈子。即使没有三目(“条件”)运算符,你已经可以创建一个难以阅读的混乱代码,而我认为它只会进一步偏离我依然怀念Pascal可读性的回忆。 - Warren P
2
现代化的 Delphi 实现中,IfThen 是内联的,因此只评估所选择的选项。 - David Heffernan
3
@RobKennedy 我在胡说八道。编译器的内联器确保始终评估可能具有副作用的任何参数。编译器编写代码来对其进行评估,并将其存储在堆栈帧上,以避免内联选择所导致的语义更改。如果表达式没有副作用,则会按需进行评估。 - David Heffernan
显示剩余3条评论

6

好的。今天的WTF代码:)

如何获得类似于三元/条件函数的东西。

program Project107;
{$APPTYPE CONSOLE}

uses SysUtils;

type
  TLazyIfThen<T:record>=record
    class function IfThen(aCondition:Boolean;aIfTrue, aIfFalse:TFunc<T>):T; static;
  end;

  class function TLazyIfThen<T>.IfThen(aCondition:Boolean;aIfTrue, aIfFalse:TFunc<T>):T;
  begin
    if aCondition then
      Result := aIfTrue
    else
      Result := aIfFalse
  end;

begin
  WriteLn(
    TLazyIfThen<Integer>.IfThen(
      True,
      function:Integer begin result := 0 end,
      function:Integer begin result := 1 end
    )
  );
  ReadLn;
end.

是的,它或多或少是无用的,但它表明可以做到。


我认为您误解了 ?: 运算符的作用--使代码更加紧凑。 ;) - Andreas Rejbrand
4
嘿嘿..在这里的代码高尔夫比赛中,Delphi从来都不是真正的赢家。 - Wouter van Nifterick
1
我开始写答案,但发现它几乎与这个答案完全相同。我认为 class function IfThen<T> 更好一些,因为编译器将优化掉所有不使用 TLazyIfThen 的可能类型。你可以让 aIfTrue 是类型 T,以节省一些打字。同意的是,这不会使代码看起来很漂亮。 - LU RD
1
@LURD:你可以使用T代替TFunc<T>,但这样一来,你就无法通过匿名方法进行惰性求值了,而且它更像是ifthen()的通用版本。至于将其编写为ifthen<T>:你是指它会导致更小的可执行文件吗? - Wouter van Nifterick

5

这里有一份关于此事的QC报告(8451),其中有一个合理的讨论。

该问题在2004年6月提出,但似乎没有Borland/CodeGear/Embarcadero的任何回应。


1
它有25个投票,评分为3.81;可能不足以使其出现在新版本的功能列表上。 - Jeroen Wiert Pluimers

5

重载的IFTHEN函数有许多可用的简单类型句柄。

StrUtils.IfThenString

Math.IfThenInteger

Math.IfThenInt64

Math.IfThenDouble)(也适用于TDateTime

尽管这种模式在Andreas评论的示例中存在问题,但对于简单类型来说,这已经足够合理了。它遵循了Delphi/Pascal方法的约定,而不是采用C语言尽可能使用最少字符的方式。

个人而言,我宁愿不要在Delphi中引入条件运算符(即?:),因为我更喜欢Delphi/Pascal的可读性,而不是C及其衍生语言。我希望看到更多创新的Delphi类型解决方案,而不是实现更多C风格的东西。


小抱怨:我曾经看到过在类C语言中即使只是使用了很少的?三元运算符,代码也非常难以阅读。主要原因是它又是一个被使用的符号,而大多数使用它的人都忘记为可读性添加括号或缩进。它可以带来的速度提升几乎无法实现:大多数情况下只是替换了单个if/then/else,而真正的改进只能在复杂表达式中获得,这些表达式很少有人能够维护。 - Jeroen Wiert Pluimers

4

Delphi中没有条件运算符,我严重怀疑它是否会有一个,但你永远不知道。您可以随时在Embarcadero提出请求。

另一种选择是定义Iff函数:

function Iff(const ACondition: Boolean; const ATrueValue, AFalseValue: XXX): XXX;
begin
  if ACondition then
    Result := ATrueValue
  else
    Result := AFalseValue;
end;

XXX代表所需类型。

用法如下:

Result := Result + Iff(combo.text='', 'null', quotedStr(combo.text));

有几个原因不建议使用条件运算符。其中之一是可读性。Pascal(以及Delphi)更注重可读性,而C语法语言更注重字符能力(尽可能多的信息)。条件运算符功能强大,但(根据某些人的说法)不易读懂。但是如果您看一下Delphi中(可怕的)with语句……(无需再说)。
另一个原因是条件运算符并非必需。这是真的。但还有其他不必要的东西被实现了。
最终只是品味问题。
但是如果您只想评估一个参数,可以始终使用以下内容,这违反了可读性和字符能力的概念:
[overdesignmode]
// Please don't take this that serious.
type
  TFunc = function(): XXX;
function Iff(const ACondition: Boolean; const ATrueFunc, AFalseFunc: TFunc): XXX;
begin
  if ACondition then
    ATrueFunc
  else
    AFalseFunc;
end;

[/overdesignmode]


8
不幸的是,这并不像条件运算符应该工作的那样,因为所有函数参数都将被评估。一个条件运算符应该评估测试,然后只评估两个剩余的表达式之一。 - anon
5
因为 Iff 是一个函数,所以 ATrueValue 和 AFalseValue 都会被评估。因此 "(o != null) ? o.GetValue() : null" 不能被翻译成 "Iff(o <> nil, o.GetValue, nil)",因为 "o.GetValue" 会导致程序崩溃。 - Andreas Hausladen
2
由于Delphi最近获得了带有返回值的exit(这也不是严格必要的,但具有更高的“特征功率”),也许三元运算符也有一些希望。 - mghie
2
是的,这并不是必需的;但同样也不是“case”语句 :) - glob
1
“with”语句可以安全而明智地使用,通过消除冗余的重复,使代码更易读,例如:使用“with cmbNames do ItemIndex := Items.IndexOf[sSomeName]”代替“cmbNames.ItemIndex := cmbNames.Items.IndexOf[sSomeName]”。但是,像所有教条一样,如果我们要进行明智的讨论,而不仅仅是机械地重复教条,就需要更多的解释。 - Deltics
显示剩余9条评论

4

2

另一个选择是使用泛型:

Cond<T> = class
    public class function IIF(Cond: boolean; IfVal: T; ElseVal: T): T;
  end;

implementation

class function Cond<T>.IIF(Cond: boolean; IfVal, ElseVal: T): T;
begin
  if Cond then
    Result := IfVal
  else
    Result := ElseVal;
end;

这篇文章很易懂:

var MyInt: Integer;
begin
  MyInt:= Cond<Integer>.IIF(someCondition, 0, 42);

注意:正如Alan Bryant在QR 8451(于2004年6月21日上午7:26:21)中指出的那样,这将始终计算所有3个参数 - 因此请注意,它不是真正的三元运算符。


1
实际上,对于字符串,您可以使用 StrUtils.IfThen 函数:
function IfThen(AValue: Boolean;
        const ATrue: string;
        AFalse: string = ): string; overload;

请查看 Delphi 帮助文档:http://docwiki.embarcadero.com/VCL/zh-cn/StrUtils.IfThen

它正好符合您的需求。


我会用这个,只是为了尝试一些“新”的东西 :) - Peter Perháč
2
不,它不会。如果传递给函数的ATrue和AFalse值是函数,则在调用IfThen()之前两个真和假函数都将被执行和评估。条件运算符评估与条件表达式相对应的真或假分支中的表达式。 - Deltics

0

如果有帮助的话,大多数情况下使用Math.IfThen函数的好处是它使用“内联”(假设编译器支持)。

使用TFunc或TFunc<T,T>版本时会有点丑陋,因为Delphi没有Lambda表达式(尽管我发现一些使用Variant表达式的实验)用于其匿名方法。

unit Zoomicon.Generics.Functors;

interface
  uses SysUtils; //for TPredicate

type
  TF = class
    class function Iff<T>(const Condition: Boolean; const ValueIfTrue, ValueIfFalse: T): T; overload; inline;
    class function Iff<T>(const Condition: Boolean; const ValueIfTrue, ValueIfFalse: TFunc<T>): T; overload; inline;
    class function Iff<T>(const Value: T; const Condition: TPredicate<T>; const ValueIfTrue, ValueIfFalse: T): T; overload; inline;
    class function Iff<T>(const Value: T; const Condition: TPredicate<T>; const ValueIfTrue, ValueIfFalse: TFunc<T>): T; overload; inline;
    class function Iff<T>(const Value: T; const Condition: TPredicate<T>; const ValueIfTrue, ValueIfFalse: TFunc<T,T>): T; overload; inline;
  end;

implementation

class function TF.Iff<T>(const Condition: Boolean; const ValueIfTrue, ValueIfFalse: T): T;
begin
  if Condition then
    result := ValueIfTrue
  else
    result := ValueIfFalse;
end;

class function TF.Iff<T>(const Condition: Boolean; const ValueIfTrue, ValueIfFalse: TFunc<T>): T;
begin
  if Condition and Assigned(ValueIfTrue) then
    result := ValueIfTrue
  else
    result := ValueIfFalse; //Note: will fail if ValueIfFalse is not assigned
end;

class function TF.Iff<T>(const Value: T; const Condition: TPredicate<T>; const ValueIfTrue, ValueIfFalse: T): T;
begin
  if Assigned(Condition) then
    result := Iff(Condition(Value), ValueIfTrue, ValueIfFalse)
  else
    result := ValueIfFalse;
end;

class function TF.Iff<T>(const Value: T; const Condition: TPredicate<T>; const ValueIfTrue, ValueIfFalse: TFunc<T>): T;
begin
  //result := Iff(Value, Condition, ValueIfTrue(), ValueIfFalse()); //use of () seems to be needed else compiler seems to match the same function (infinite recursion) //DOESN'T COMPILE (probably Delphi bug)
  if Assigned(Condition) then
    result := Iff(Condition(Value), ValueIfTrue, ValueIfFalse) //TODO: not sure if evaluation is deferred here (aka which "Iff" gets called [CTRL+Click in Delphi doesn't seem that clever], @ValueIfTrue/@ValueIfFalse to force that don't seem to compile)
  else
    result := ValueIfFalse; //Note: will fail if ValueIfFalse is not assigned
end;

class function TF.Iff<T>(const Value: T; const Condition: TPredicate<T>; const ValueIfTrue, ValueIfFalse: TFunc<T,T>): T;
begin
  //result := Iff(Value, Condition, ValueIfTrue(Value), ValueIfFalse(Value)); //DOESN'T COMPILE (probably Delphi bug)
  if Assigned(Condition) and Assigned(ValueIfTrue) {and Assigned(ValueIfFalse)} then //no need to check Assigned(ValueIfFalse) here, since in any case it will fail
    result := Iff(Condition(Value), ValueIfTrue(Value), ValueIfFalse(Value)) //Note: will fail if ValueIfFalse is not assigned
  else
    result := ValueIfFalse(Value); //Note: will fail if ValueIfFalse is not assigned
end;

end.

可以在Zoomicon的GitHub页面https://github.com/Zoomicon/READCOM_App(正在开发中的应用程序)下找到Generics。 - George Birbilis

0

程序员的 WTF 代码第二弹:

program TernaryOpTest;

uses
  System.SysUtils, Vcl.Dialogs;

type
  TGetValue = reference to function(): Double;

function TernaryOp(condition: Boolean; trueFunc, falseFunc: TGetValue): Double;
begin
  if condition then begin
    if Assigned(trueFunc) then Result := trueFunc() else raise EArgumentNilException.Create('trueFunc not defined.');
  end
  else begin
    if Assigned(falseFunc) then Result := falseFunc() else raise EArgumentNilException.Create('falseFunc not defined.');
  end;
end;

procedure TernaryTest(x: Double);
var
  v: Double;
begin
  v := TernaryOp(x <> 0, function(): Double begin Result := 1/x; ShowMessage('True case'); end, function(): Double begin Result := 0; ShowMessage('False case'); end);
  ShowMessage(FloatToStr(v));
end;

begin
  ShowMessage('Testing true case');
  TernaryTest(10);
  ShowMessage('Testing false case');
  TernaryTest(0);
  ShowMessage('Testing exception');
  TernaryOp(False, nil, nil);
end.

Variant 数据类型的一种可能修改是:

type
  TGetValue = reference to function(): Variant;

function TernaryOp(condition: Boolean; trueFunc, falseFunc: TGetValue): Variant;
begin
  Result := Unassigned;
  if condition then begin
    if Assigned(trueFunc) then Result := trueFunc();
  end
  else begin
    if Assigned(falseFunc) then Result := falseFunc();
  end;
end;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接