【问题标题】:Problem assigning null to simulated generic nullable data types将 null 分配给模拟的通用可为空数据类型的问题
【发布时间】:2020-09-23 15:01:48
【问题描述】:

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

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;

你有什么想法吗?

【问题讨论】:

  • T,因此 Value 可以是任何类型,包括变量与赋值不兼容的东西,例如记录。我知道(好吧,假设)你只打算使用兼容类型,但编译器不知道
  • 当然有很多方法可以创建TNullable&lt;T&gt;。就个人而言,我认为我会在不涉及变体的情况下这样做。所以我的空符号不是null,而是自定义类型。
  • 一个空记录就足够了,可以作为单独的类型,也可以作为TNullable 的嵌套类型。另一种方法是简单地将SetNull() 方法添加到TNullable
  • @Paul null 是 C# 中的实际关键字,类似于 C++11 中的 nullptr。它们都是不同的,并且由编译器进行特殊处理。目前在 Delphi 中最接近的东西是 nil,但如果 T 是引用类型,这可能会导致冲突。因此,在 Delphi 中,如果您想对 TNullable 值使用赋值语法,则为您的 null 值声明一个不同的类型(例如空记录)是下一个最佳选择。
  • 存在严重的设计问题:1) 您应该在private 部分隐藏 TNullable 记录内部。 2) 在IsNull 字段中保持空状态不是一个好主意。当您构造default(TNullable&lt;T&gt;) 时,它的IsNull 将初始化为false,这可能不是您想要的。你最好叫它HasValue .. similar to C#.

标签: delphi nullable delphi-xe5


【解决方案1】:

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;

【讨论】:

  • T 上使用record constraint 有意义吗?大概 OP 只希望这个值用于值类型。
  • @J... 可能是这样,但这确实取决于 OP 的用例。例如,OP 正在寻找类似于 C# 可空对象的东西,而 C# 8 添加了可空引用类型的概念。
  • 假设我们可以允许分配nil(使用适当的运算符)而不是TNullValue,如果约束排除引用类型为T,但是......假设OP想要这种行为。 Delphi 对象已经是“可空的”,这将允许nil 将空分配给值和引用类型。当然,这并不能区分 null 和 unassigned。只是不确定这样的设计是否还有其他后果......
  • @J... 如果T 允许引用类型,那么允许nil 将需要接受Pointer 的操作员,这将与接受T 的操作员发生冲突,并且您遇到了问题决定分配nil 是否将HasValue 设置为True 或False。 “当然,这并不能区分 null 和未分配” - 完全正确。对于引用类型,完全没有值的可空值与拥有nil 值的可空值之间会有区别。
  • 是的,当然。它需要 T 上的 record 约束来排除这种情况。
猜你喜欢
  • 1970-01-01
  • 2022-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-25
  • 2021-07-14
相关资源
最近更新 更多