admin管理员组

文章数量:1344584

I have created a nullable type like this, which I found on an SO answer, don't remember which.

unit NullableType;

interface

uses
  System.SysUtils, System.Rtti;

type
  TNullable<T> = record
 private
    FValue: T;
    FHasValue: IInterface;
    function GetHasValue: Boolean;
    function GetValue: T;
    procedure SetValue(const AValue: T);
  public
    constructor Create(AValue: T);
    function ToString: string; // <-- add this for easier use!
    property HasValue: Boolean read GetHasValue;
    property Value: T read GetValue write SetValue;
  end;


implementation


constructor TNullable<T>.Create(AValue: T);
begin
   SetValue(AValue);
end;

function TNullable<T>.GetHasValue: Boolean;
begin
  Result := FHasValue <> nil;
end;

function TNullable<T>.GetValue: T;
begin
  if HasValue then
    Result := FValue
  else
    Result := Default(T);
end;

procedure TNullable<T>.SetValue(const AValue: T);
begin
  FValue := AValue;
  FHasValue := TInterfacedObject.Create;
end;

function TNullable<T>.ToString: string;
begin
  if HasValue then
  begin
    if TypeInfo(T) = TypeInfo(TDateTime) then
      Result := DateTimeToStr(PDateTime(@FValue)^)
    else if TypeInfo(T) = TypeInfo(TDate) then
      Result := DateToStr(PDateTime(@FValue)^)
    else if TypeInfo(T) = TypeInfo(TTime) then
      Result := TimeToStr(PDateTime(@FValue)^)
    else
      Result := TValue.From<T>(FValue).ToString;
  end
  else
    Result := 'null';
end;

end.

My problem is I don't know how to set it to null.
For example

var
  id : TNullable<integer>;
begin
  if Edit1.Text <> '' then
    id.Value := StrToInt(Edit1.Text)
  else
    id.Value := null;  // runtime error

this gives me runtime error

Could not convert variant of type (Null) into type (Integer)

It's been a while since I programmed in Delphi, and I just can't figure out how to set value of the id variable to null

id.Value := nil;

gives compiler error

Incompatible types: 'integer' and 'pointer'

Only by not setting the value of id I am able to have its value null, but what if I want to set it to any value, including null? How to do that?

I have created a nullable type like this, which I found on an SO answer, don't remember which.

unit NullableType;

interface

uses
  System.SysUtils, System.Rtti;

type
  TNullable<T> = record
 private
    FValue: T;
    FHasValue: IInterface;
    function GetHasValue: Boolean;
    function GetValue: T;
    procedure SetValue(const AValue: T);
  public
    constructor Create(AValue: T);
    function ToString: string; // <-- add this for easier use!
    property HasValue: Boolean read GetHasValue;
    property Value: T read GetValue write SetValue;
  end;


implementation


constructor TNullable<T>.Create(AValue: T);
begin
   SetValue(AValue);
end;

function TNullable<T>.GetHasValue: Boolean;
begin
  Result := FHasValue <> nil;
end;

function TNullable<T>.GetValue: T;
begin
  if HasValue then
    Result := FValue
  else
    Result := Default(T);
end;

procedure TNullable<T>.SetValue(const AValue: T);
begin
  FValue := AValue;
  FHasValue := TInterfacedObject.Create;
end;

function TNullable<T>.ToString: string;
begin
  if HasValue then
  begin
    if TypeInfo(T) = TypeInfo(TDateTime) then
      Result := DateTimeToStr(PDateTime(@FValue)^)
    else if TypeInfo(T) = TypeInfo(TDate) then
      Result := DateToStr(PDateTime(@FValue)^)
    else if TypeInfo(T) = TypeInfo(TTime) then
      Result := TimeToStr(PDateTime(@FValue)^)
    else
      Result := TValue.From<T>(FValue).ToString;
  end
  else
    Result := 'null';
end;

end.

My problem is I don't know how to set it to null.
For example

var
  id : TNullable<integer>;
begin
  if Edit1.Text <> '' then
    id.Value := StrToInt(Edit1.Text)
  else
    id.Value := null;  // runtime error

this gives me runtime error

Could not convert variant of type (Null) into type (Integer)

It's been a while since I programmed in Delphi, and I just can't figure out how to set value of the id variable to null

id.Value := nil;

gives compiler error

Incompatible types: 'integer' and 'pointer'

Only by not setting the value of id I am able to have its value null, but what if I want to set it to any value, including null? How to do that?

Share Improve this question edited yesterday mjn 36.7k30 gold badges184 silver badges385 bronze badges asked yesterday GuidoGGuidoG 12.1k6 gold badges55 silver badges97 bronze badges 0
Add a comment  | 

2 Answers 2

Reset to default 3

null is a const Variant of type VT_NULL, that is why you are getting a runtime error related to a Variant conversion. You want nil instead.

However, you can't assign a nil to a T when T is not a pointer type. So, to do what you want, you need to update Nullable<T> to accept T^ pointers as input.

From Delphi 2006 onward, a record can overload operators, so you don't need to accept assignments via a Value property. You can overload conversion operators that will allow you to convert T values and T^ pointers into Nullable<T> (and convert Nullable<T> into T values), then you will be able to assign nil pointers to your Nullable variables, eg:

unit NullableType;

interface

type
  TNullable<T> = record
  public
    type PointerOfT = ^T; // <-- add this
  private
    FValue: T;
    FHasValue: IInterface;

    function GetHasValue: Boolean;

    procedure SetValue(const AValue: T);
    procedure SetValueByPointer(const AValue: PointerOfT);
    procedure SetToNil;
  public
    constructor Create(const AValue: T); overload;
    constructor Create(const AValue: PointerOfT); overload; // <-- add this

    // add these...
    class operator Implicit(const Src: TNullable<T>): T;
    class operator Implicit(const Src: T): TNullable<T>;
    class operator Implicit(const Src: PointerOfT): TNullable<T>;

    class operator Explicit(const Src: TNullable<T>): T;
    class operator Explicit(const Src: T): TNullable<T>;
    class operator Explicit(const Src: PointerOfT): TNullable<T>;
    //

    property HasValue: Boolean read GetHasValue;
    property Value: T read FValue write SetValue; // <-- optional now!

    function ToString: string;
  end;

implementation

uses
  System.SysUtils, System.Rtti;

constructor TNullable<T>.Create(const AValue: T);
begin
  SetValue(AValue);
end;

constructor TNullable<T>.Create(const AValue: PointerOfT);
begin
  SetValueByPointer(AValue);
end;

class operator TNullable<T>.Implicit(const Src: TNullable<T>): T;
begin
  Result := Src.FValue;
end;

class operator TNullable<T>.Implicit(const Src: T): TNullable<T>;
begin
  Result.SetValue(Src);
end;

class operator TNullable<T>.Implicit(const Src: PointerOfT): TNullable<T>;
begin
  Result.SetValueByPointer(Src);
end;

class operator TNullable<T>.Explicit(const Src: TNullable<T>): T;
begin
  Result := Src.FValue;
end;

class operator TNullable<T>.Explicit(const Src: T): TNullable<T>;
begin
  Result.SetValue(Src);
end;

class operator TNullable<T>.Explicit(const Src: PointerOfT): TNullable<T>;
begin
  Result.SetValueByPointer(Src);
end;

procedure TNullable<T>.SetValue(const AValue: T);
begin
  FValue := AValue;
  FHasValue := TInterfacedObject.Create;
end;

procedure TNullable<T>.SetValueByPointer(const AValue: PointerOfT);
begin
  if AValue <> nil then
    SetValue(AValue^)
  else
    SetToNil;
end;

procedure TNullable<T>.SetToNil;
begin
  FValue := Default(T);
  FHasValue := nil;
end;

function TNullable<T>.ToString: string;
begin
  if HasValue then
  begin
    if TypeInfo(T) = TypeInfo(TDateTime) then
      Result := DateTimeToStr(PDateTime(@FValue)^)
    else if TypeInfo(T) = TypeInfo(TDate) then
      Result := DateToStr(PDateTime(@FValue)^)
    else if TypeInfo(T) = TypeInfo(TTime) then
      Result := TimeToStr(PDateTime(@FValue)^)
    else
      Result := TValue.From<T>(FValue).ToString;
  end
  else
    Result := 'null';
end;

end.
var
  id : TNullable<integer>;
begin
  if Edit1.Text <> '' then
    id := StrToInt(Edit1.Text)
  else
    id := nil;

Also, from Delphi 10.4 onward, you can use a Custom Managed Record to replace the IInterface with a simple Boolean, eg:

  • The use of IInterface is based on an old blog article written by Allen Bauer that predates the introduction of CMRs. He even states in the article that CMRs would have addressed the issue that he was using IInterface as a workaround for!
unit NullableType;

interface

type
  TNullable<T> = record
  public
    type PointerOfT = ^T;
  private
    FValue: T;
    FHasValue: Boolean; // <-- change this

    procedure SetValue(const AValue: T);
    procedure SetValueByPointer(const AValue: PointerOfT);
    procedure SetToNil;
  public
    constructor Create(const AValue: T); overload;
    constructor Create(const AValue: PointerOfT); overload;

    class operator Initialize(out Dest: TNullable<T>); // <-- add this

    class operator Implicit(const Src: TNullable<T>): T;
    class operator Implicit(const Src: T): TNullable<T>;
    class operator Implicit(const Src: PointerOfT): TNullable<T>;
    class operator Explicit(const Src: TNullable<T>): T;
    class operator Explicit(const Src: T): TNullable<T>;
    class operator Explicit(const Src: PointerOfT): TNullable<T>;

    property HasValue: Boolean read FHasValue;
    property Value: T read FValue write SetValue;

    function ToString: string;
  end;

implementation

uses
  System.SysUtils, System.Rtti;

constructor TNullable<T>.Create(const AValue: T);
begin
  SetValue(AValue);
end;

constructor TNullable<T>.Create(const AValue: PointerOfT);
begin
  SetValueByPointer(AValue);
end;

class operator TNullable<T>.Initialize(out Dest: TNullable<T>);
begin
  Dest.SetToNil;
end;

class operator TNullable<T>.Implicit(const Src: TNullable<T>): T;
begin
  Result := Src.FValue;
end;

class operator TNullable<T>.Implicit(const Src: T): TNullable<T>;
begin
  Result.SetValue(Src);
end;

class operator TNullable<T>.Implicit(const Src: PointerOfT): TNullable<T>;
begin
  Result.SetValueByPointer(Src);
end;

class operator TNullable<T>.Explicit(const Src: TNullable<T>): T;
begin
  Result := Src.FValue;
end;

class operator TNullable<T>.Explicit(const Src: T): TNullable<T>;
begin
  Result.SetValue(Src);
end;

class operator TNullable<T>.Explicit(const Src: PointerOfT): TNullable<T>;
begin
  Result.SetValueByPointer(Src);
end;

procedure TNullable<T>.SetValue(const AValue: T);
begin
  FValue := AValue;
  FHasValue := True;
end;

procedure TNullable<T>.SetValueByPointer(const AValue: PointerOfT);
begin
  if AValue <> nil then
    SetValue(AValue^)
  else
    SetToNil;
end;

procedure TNullable<T>.SetToNil;
begin
  FValue := Default(T);
  FHasValue := False;
end;

function TNullable<T>.ToString: string;
begin
  if HasValue then
  begin
    if TypeInfo(T) = TypeInfo(TDateTime) then
      Result := DateTimeToStr(PDateTime(@FValue)^)
    else if TypeInfo(T) = TypeInfo(TDate) then
      Result := DateToStr(PDateTime(@FValue)^)
    else if TypeInfo(T) = TypeInfo(TTime) then
      Result := TimeToStr(PDateTime(@FValue)^)
    else
      Result := TValue.From<T>(FValue).ToString;
  end
  else
    Result := '(null)';
end;

end.

If you need to support older Delphi versions, then just IFDEF the code accordingly.

Since you use an interface to detect if it has a value or not, I'd do this:

PROCEDURE TNullable<T>.SetNull;
  BEGIN
    FHasValue:=NIL
  END;

FUNCTION TNullable<T>.IsNull : BOOLEAN;
  BEGIN
    Result:=NOT Assigned(FHasValue)
  END;

You can't do it by assignment (or maybe you can - declare an assignment operator that accepts a pointer value but only allows NIL as value, and then call SetNull. If you try to assign a non-NIL pointer, raise an exception).

本文标签: delphiHow to set a nullable integer to nullStack Overflow