在Delphi中,如何使用Default(T)测试泛型类型变量的相等性?

7
我想编写一个通用的缓存属性访问器,就像以下代码一样,但是当我尝试检查存储变量是否已经包含值时,会出现编译器错误:
function TMyClass.GetProp<T>(var ADataValue: T; const ARetriever: TFunc<T>): T;
begin
  if ADataValue = Default(T) then // <-- compiler error on this line
    ADataValue := ARetriever();
  Result := ADataValue;
end;

我收到的错误是"E2015 操作符不适用于此操作数类型"。
我需要在T上加一个约束条件才能使它工作吗?帮助文件说Default()可以接受任何东西,除了泛型类型。在我的情况下,我主要处理像StringIntegerTDateTime这样的简单类型。
还是有其他库函数可以执行这个特定的检查吗?
我正在使用Delphi 2009,如果有影响的话。
附注:以防代码没有清晰地表明我想做什么:在我的情况下,由于各种原因确定实际属性值可能需要一些时间,有时我甚至可能根本不需要它们。但好的一面是这些值是不变的,所以我只想在第一次访问该属性时调用确定实际值的代码,然后将该值存储在类字段中,并在下一次访问该属性时直接返回缓存的值。以下是我希望能够使用该代码的示例:
type
  TMyClass = class
  private
    FSomeProp: String;
    function GetSomeProp: String;

    function GetProp<T>(var ADataValue: T; const ARetriever: TFunc<T>): T;
  public
    property SomeProp read GetSomeProp;
  end;

function GetSomeProp: String;
begin
  Result := GetProp<String>(FSomeProp,
                            function: String
                            begin
                              Result := SomeSlowOrExpensiveCalculation;
                            end);
end;

显然,不止一个属性。


顺便说一句,这是个好主意。 - jpfollenius
2
你应该使用可空类型,否则代码将不断重新评估已经缓存但具有默认值的属性。例如,请参考http://blogs.embarcadero.com/abauer/2008/09/18/38869 - mghie
@mghie:很好的观点,通常我会同意,但在这种特定情况下,默认值可以安全地始终被解释为“未初始化”。 - Oliver Giesen
2个回答

13

在Binis的评论中得到了提示,并在Generics.Collections中进行了一些挖掘,我得出了以下结果,它似乎正如我所想:

function TMyClass.GetProp<T>(var ADataValue: T; const ARetriever: TFunc<T>): T;
var
  lComparer: IEqualityComparer<T>;
begin
  lComparer := TEqualityComparer<T>.Default;
  if lComparer.Equals(ADataValue, Default(T)) then
    ADataValue := ARetriever();
  Result := ADataValue;
end;

3
值得注意的是,“IEqualityComparer”现在包含在Generics.Defaults中(不确定自从哪个版本开始,使用Berlin 10.1)。 - Zulukas

0
问题不在于 Default 函数,而在于等号运算符 =
您可以将 T 约束为 IEquatable 并使用 Equals 方法,如下所示:
TMyClass = class
  function GetProp<T : IEquatable<T>>(var ADataValue: T; const ARetriever: 
end;
...
function TMyClass.GetProp<T>(var ADataValue: T; const ARetriever: TFunc<T>): T;
begin  
if ADataValue.Equals (Default(T)) then
  ADataValue := ARetriever();  
Result := ADataValue;
end;   

不确定为什么,但是在这段代码中我得到了一个“未声明的标识符'IEquatable'”错误(尽管我可以看到IEquatable确实在System.pas中声明)。然而,我怀疑这对我来说也不会起作用,因为我不知道像StringTDateTimeInteger这样的基元类型是否会隐式支持该接口... - Oliver Giesen
@Oliver:你说得对,这不能用于原始类型。不幸的是,我不知道其他的解决方案。让我们看看其他人能否提供帮助。我不知道为什么使用“IEquatable”会出现错误信息。 - jpfollenius
实际上,在 System.pas 中声明的不是 IEquatable,而是 IEquatable<T>,这解释了错误的原因... GetProp<T: IEquatable<T>>(... 确实可以编译,但是当将 String 传递给 T 时,就会如预期的那样出现错误:"类型参数 'T' 必须支持接口 IEquatable<System.string>"。 - Oliver Giesen

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