将 null 分配给模拟的通用可为 null 的数据类型时出现问题

Problem assigning null to simulated generic nullable data types

我正在尝试在 Delphi 中创建可为 null 的数据类型:

type
  TNullable<T> = record
  public
    Value: T;
    IsNull: Boolean;
    class operator Implicit(const AValue: T): TNullable<T>;
    class operator Implicit(const AValue: TNullable<T>): T;
    class operator Implicit(const AValue: Variant): TNullable<T>;
    class operator Explicit(const AValue: T): TNullable<T>;
  end;

到目前为止一切顺利,但是要将什么分配为 null 文字,以便可空数据类型保持其基本类型?例如:

var
  v: TNullable<Integer>;
begin
  //What type is this "null"? A Variant null?
  //How TNullable<Integer> could remain of Integer after the assignment?
  v := null;

  //How to compare this "null"? Compare to what type?
  if v = null then begin
  end;
end;

让我们假设 null 是变体 null:

class operator TNullable<T>.Implicit(const AValue: Variant): TNullable<T>;
begin
  if VarIsNull(AValue) or VarIsClear(AValue) then begin
    Result.IsNull := True;
    Result.Value := Default(T);
  end
  else begin
    Result.IsNull := False;
    Result.Value := AValue; //Version 1: Incompatible types: 'T' and 'Variant'!!!
    Result.Value := T(AValue); //Version 2: Invalid typecast!!!
    //Should I write a big "case" block here in order to handle each data type?!
  end;
end;

你有什么想法吗?

Null 在这种情况下确实是 Variant,参见 System.Variants.Null。在这种情况下使用 Variant 不是一个好主意,部分原因是您遇到的分配问题。

更好的选择是定义一个不同的类型来表示您的 null 值(类似于 C++11 及更高版本中的 nullptr_t),例如:

type
  TNullValue = record
  end;

  TNullable<T{: record}> = record
  public
    Value: T;
    HasValue: Boolean;

    class operator Implicit(const AValue: T): TNullable<T>;
    class operator Implicit(const AValue: TNullable<T>): T;
    class operator Implicit(const AValue: TNullValue): TNullable<T>;
    class operator Explicit(const AValue: T): TNullable<T>;

    // add these...
    class operator Equal(const A: TNullable<T>; const B: TNullValue): Boolean;
    class operator NotEqual(const A: TNullable<T>; const B: TNullValue): Boolean;
    ...
  end;

const
  NullValue: TNullValue;

...

class operator TNullable<T>.Implicit(const AValue: T): TNullable<T>;
begin
  Result.Value := AValue;
  Result.HasValue := True;
end;

class operator TNullable<T>.Implicit(const AValue: TNullable<T>): T;
begin
  if AValue.HasValue then
    Result := AValue.Value
  else
    Result := Default(T); // or raise an exception
end;

class operator TNullable<T>.Implicit(const AValue: TNullValue): TNullable<T>;
begin
  Result.Value := Default(T);
  Result.HasValue := False;
end;

class operator TNullable<T>.Explicit(const AValue: T): TNullable<T>;
begin
  Result.Value := AValue;
  Result.HasValue := True;
end;

class operator TNullable<T>.Equal(const A: TNullable<T>; const B: TNullValue): Boolean;
begin
  Result := not A.HasValue;
end;

class operator TNullable<T>.NotEqual(const A: TNullable<T>; const B: TNullValue): Boolean;
begin
  Result := A.HasValue;
end;
var
  v: TNullable<Integer>;
begin
  v := NullValue;

  if v = NullValue then begin
    ...
  end;

  if v <> NullValue then begin
    ...
  end;
end;