Sometimes we want an optional parameter
function doSomething(foo:Integer; bar:TObject=nil)
begin
if bar <> nil then // do something optional with bar
....
end
How do I do the equivalent with a boolean, that allows me to differentiate between the two boolean values and "no value"?
function doSomething(foo:Integer; bar:Boolean=nil) // Won't compile
begin
if bar <> nil then // do something optional with bar
end
Obviously this won't compile as a Boolean cannot be nil.
Basica开发者_StackOverflowlly, I want a parameter with three possible states; true, false or "unspecified".
You can do this other way, using overloading:
function doSomething(foo:Integer): Boolean; overload;
begin
// do something without bar
end;
function doSomething(foo:Integer; bar:Boolean): Boolean; overload
begin
// do something optional with bar
end;
Then you can use it as doSomething(1)
, as well as doSomething(1, true)
Using your example, it will be equivalent to:
function doSomething(foo:Integer; bar:Boolean=nil): Boolean; // Won't compile
begin
if bar <> nil then
// do something optional with bar
else
// do something without bar
end;
Values of the Boolean type can only be True or False. You can define your own type that has three states: True, False, and Unspecified:
type ThreeStateBoolean = (True, False, Unspecified);
Or, you can pass a pointer to a Boolean:
type PBoolean = ^Boolean;
function doSomething(bar: PBoolean = nil)
begin
if bar <> nil then
// do something with bar^
end
Passing a pointer to a Boolean might be awkward depending on how you're calling it.
Another option (if you have a relatively modern version to Delphi) is to implement this as a record, with implicit conversion to and from boolean values. With operator overloading, you can also enable 3-state logic. This is overkill if all you require is occasional use, but if you do need a three-state logic system it works very nicely, particularly as you can assign boolean values to it. Be careful with assignments from 3-state to 2-state thought. The example below assigns False to a Boolean <- 'Troolean' assignation where the troolean is TNil, as per an unassigned boolean in Delphi, but there are obvious complications.
Please note that this is not a complete or efficient implementation by any means, it's just a demo to indictate what is possible. Incidentally, there is a good CodeRage vidoe by Jeroen Pluimers on nullable types. This question provides a link.
unit UnitTroolean;
interface
type
TTroolean = record
private type
TThreeState = (TTrue = 1, TFalse = 0, TNil = -1);
var
fThreeState: TThreeState;
public
function AsString: string;
class operator Implicit(Value: boolean): TTroolean;
class operator Implicit(Value: TTroolean): boolean;
class operator Implicit(Value: TThreeState): TTroolean;
class operator Implicit(Value: TTroolean): TThreeState;
class operator LogicalAnd(Left, Right: TTroolean): TTroolean;
class operator LogicalOr(Left, Right: TTroolean): TTroolean;
class operator LogicalNot(Value: TTroolean): TTroolean;
end;
implementation
{ TRoolean }
class operator TTroolean.Implicit(Value: boolean): TTroolean;
begin
if Value then
result.fThreeState := TTrue
else
result.fThreeState := TFalse;
end;
class operator TTroolean.Implicit(Value: TTroolean): boolean;
begin
if not(Value.fThreeState = TNil) then
result := (Value.fThreeState = TTrue)
else
result := false;
end;
class operator TTroolean.Implicit(Value: TThreeState): TTroolean;
begin
result.fThreeState := Value;
end;
class operator TTroolean.Implicit(Value: TTroolean): TThreeState;
begin
result := Value.fThreeState;
end;
class operator TTroolean.LogicalAnd(Left, Right: TTroolean): TTroolean;
begin
if (Left.fThreeState = TNil) or (Right.fThreeState = TNil) then
result.fThreeState := TNil
else if ((Left.fThreeState = TTrue) and (Right.fThreeState = TTrue)) then
result.fThreeState := TTrue
else
result.fThreeState := TFalse;
end;
class operator TTroolean.LogicalNot(Value: TTroolean): TTroolean;
begin
begin
case value.fThreeState of
TNil: result.fThreeState:= TNil;
TTrue: result.fThreeState:= TFalse;
TFalse: result.fThreeState:= TTrue
end;
end;
end;
class operator TTroolean.LogicalOr(Left, Right: TTroolean): TTroolean;
begin
if (Left.fThreeState = TNil) or (Right.fThreeState = TNil) then
result.fThreeState := TNil
else if ((Left.fThreeState = TTrue) or (Right.fThreeState = TTrue)) then
result.fThreeState := TTrue
else
result.fThreeState := TFalse;
end;
function TTroolean.AsString: string;
begin
case ord(fThreeState) of
1:
result := 'TTrue';
0:
result := 'TFalse';
-1:
result := 'TNil';
end;
end;
end.
And an example of use
program ThreeStateLogicTest;
{$APPTYPE CONSOLE}
uses
SysUtils,
UnitTroolean in 'UnitTroolean.pas';
var
ABoolean: boolean;
ATroolean, Anothertroolean, AThirdTroolean: TTroolean;
begin
try
{ TODO -oUser -cConsole Main : Insert code here }
write('Boolean:', BoolToStr(ABoolean, true), #10#13);
write(#10#13);
ATroolean := TFalse;
ABoolean := true;
ATroolean := ABoolean;
write('Boolean:', BoolToStr(ABoolean, true), #10#13);
write('Troolean:', ATroolean.AsString, #10#13);
write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13);
write(#10#13);
ATroolean := TTrue;
ABoolean := false;
ATroolean := ABoolean;
write('Boolean:', BoolToStr(ABoolean, true), #10#13);
write('Troolean:', ATroolean.AsString, #10#13);
write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13);
write(#10#13);
ABoolean := false;
ATroolean := TTrue;
ABoolean := ATroolean;
write('Boolean:', BoolToStr(ABoolean, true), #10#13);
write('Troolean:', ATroolean.AsString, #10#13);
write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13);
write(#10#13);
ABoolean := true;
ATroolean := TFalse;
ABoolean := ATroolean;
write('Boolean:', BoolToStr(ABoolean, true), #10#13);
write('Troolean:', ATroolean.AsString, #10#13);
write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13);
write(#10#13);
ABoolean := false;
ATroolean := Tnil;
ABoolean := ATroolean;
write('Boolean:', BoolToStr(ABoolean, true), #10#13);
write('Troolean:', ATroolean.AsString, #10#13);
write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13);
write(#10#13);
ABoolean := true;
ATroolean := Tnil;
ABoolean := ATroolean;
write('Boolean:', BoolToStr(ABoolean, true), #10#13);
write('Troolean:', ATroolean.AsString, #10#13);
write('Troolean as Boolean:', BoolToStr(ATroolean, true), #10#13);
write(#10#13);
ATroolean := TTrue;
Anothertroolean := false;
AThirdTroolean := ATroolean and Anothertroolean;
write('And:', AThirdTroolean.AsString, #10#13);
AThirdTroolean := ATroolean or Anothertroolean;
write('Or:', AThirdTroolean.AsString, #10#13);
ATroolean := TNil;
Anothertroolean:= not ATroolean;
write('Not TNil:', Anothertroolean.AsString, #10#13);
ATroolean := TTrue;
Anothertroolean:= not ATroolean;
write('Not Ttrue:', Anothertroolean.AsString, #10#13);
ATroolean := Tfalse;
Anothertroolean:= not ATroolean;
write('Not Tfalse:', Anothertroolean.AsString, #10#13);
readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Or... you could use an Integer and cast it to a Boolean as necessary. Use 0 for false, 1 for true, and -1 for "nil". Anyway you slice it, you can't use a Boolean variable to do what you want, you will need another type or a manipulation of parameters as lynxnake suggested.
EDIT: A variation on this, which is very inefficient, would be to use a Variant. With a variant you can pass in Null values (similar to nil in some ways but still a value) as well as Unassigned (also similar to nil, but representing "no value").
The cleanest way is to use enumeration (a.k.a. enumerated type).
This is already shown in the Greg Hewgill's answer — but incorrectly, you shouldn't use predefined false
and true
as enumeration identifiers¹. And within the HMcG's answer — but within the wrapper type (more complex example). I suggest writing something like: type TTrilean = (triFalse, triTrue, triNull);
.
Alternatively, you can use existing TCheckBoxState
type from StdCtrls
module, if you don't mind bringing VCL modules into your project.
Additionally you can write wrapper functions per the Serhii Kheilyk's answer:
procedure DoSomething(Foo: Integer; Bar: TTrilean); overload;
begin
… //main code
end;
procedure DoSomething(Foo: Integer; Bar: Boolean); overload;
const
Trileans: array[Boolean] of TTrilean = (triFalse, triTrue);
begin
DoSomething(Foo, Trileans[Bar]);
end;
procedure DoSomething(Foo: Integer); overload;
begin
DoSomething(Foo, triNull);
end;
You can even make the first one private and the last two public, if you wish.
Notes:
1. I think (not sure), formally you can write type TMyType = (False, True, Unspecified);
, as False
and True
aren't reserved words. But this will break your access to original False
and True
of type Boolean
(you'll need to refer them as System.False
and System.True
after that). And that is not third-party-compiler-compatible (e.g. FPC won't allow that).
精彩评论