case 语句中未初始化的变量

Uninitialized variable in case statement

每当我使用以下结构时,我仍然无法弄清楚如何消除有关未初始化变量的警告,即使我知道这永远不会发生。

TCustomEnum = (ceValue1, ceValue2, ceValue3);

function DoSomething(LI_Enum: TCustomEnum): Integer;
var
  lNumber : Integer;
begin
  case LI_Enum of
    ceValue1 : lNumber := 1;
    ceValue2 : lNumber := 2;
    ceValue3 : lNumber := 3;
  end;
  Result := 2 * lNumber;
end;

W1036 Variable 'lNumber' might not have been initialized

我找到了 3 个解决方案,但我都不喜欢其中任何一个。特别是有更多的变量或语句。有没有其他方法可以避免这种情况?

  1. {$WARN USE_BEFORE_DEF OFF}{$WARN USE_BEFORE_DEF ON}
  2. 包装函数
  3. 在每个 case 语句中使用 else Exit; 并在开头使用 Result := 0
  4. 初始化每个变量,尽管永远不会使用该值

通过执行以下操作

function DoSomething(LI_Enum: TCustomEnum): Integer;
var
  lNumber : Integer;
begin
  case LI_Enum of
    ceValue1 : lNumber := 1;
    ceValue2 : lNumber := 2;
    ceValue3 : lNumber := 3;
    else raise exception.create('Oops I forgot one of the LI_Enum values') 
  end;
  Result := 2 * lNumber;
end;

也许更好的异常文本或者甚至根本不引发异常(并为 lNumber 分配不同的值),但引发异常确实有提示你的好处,比如,在你添加的行后六个月一个新的案例值。

编辑

关键是编译器是正确的。枚举的底层结构是某种形式的(无符号)整数,因此枚举完全有可能包含非法值,例如 27。在实践中有很多方法可以出现这种情况。因此,如果您正在编写完整的代码,则需要考虑到这种可能性。编译器只是警告您您没有考虑到这种可能性。

我发现这个编译器警告有点令人失望。毕竟,编译器肯定可以检测到您已经涵盖了枚举类型的所有可能值。我不认为应该担心您在枚举类型中放入了无效的序数,如果这确实是此警告背后的想法。

无论如何,我个人使用以下辅助方法来处理这个问题:

procedure RaiseAssertionFailed; overload;
procedure RaiseAssertionFailed(var v1); overload;
procedure RaiseAssertionFailed(var v1,v2); overload;

....

procedure DoRaiseAssertionFailed;
begin
  raise EAssertionFailed.CreateFmt(
    'A critical error has occurred:'+ sLineBreak + sLineBreak +
    '      Assertion failed at %p.'+ sLineBreak + sLineBreak +
    'In order to avoid invalid results or data corruption please close the program and report '+
    'the above error code along with any other information relating to this problem.',
    [ReturnAddress]
  ) at ReturnAddress;
end;

procedure RaiseAssertionFailed;
asm
  JMP    DoRaiseAssertionFailed;
end;

procedure RaiseAssertionFailed(var v1);
asm
  JMP    DoRaiseAssertionFailed;
end;

procedure RaiseAssertionFailed(var v1,v2);
asm
  JMP    DoRaiseAssertionFailed;
end;

您的代码将变为:

function DoSomething(LI_Enum: TCustomEnum): Integer;
var
  lNumber : Integer;
begin
  case LI_Enum of
    ceValue1 : lNumber := 1;
    ceValue2 : lNumber := 2;
    ceValue3 : lNumber := 3;
  else
    RaiseAssertionFailed(lNumber);
  end;
  Result := 2 * lNumber;
end;

这与@Dsm 概述的方法非常相似。如果您使用该方法,那么编译器可以看到您正在引发异常,并且知道 lNumber 不需要初始化。

我更喜欢将异常的引发包装到一个共享函数中。这样我就不需要一次又一次地写同样的错误信息。 DRY 原则的应用。

但是,如果您这样做,并将 raise 移动到共享函数中,则编译器无法确定该函数将引发异常。因此是无类型的 var 参数。这允许您标记变量可能被修改,从而抑制编译器警告。

另一种方法是声明一个异常 class,在其无参数构造函数中提供文本。

type
  EInternalError = class(Exception)
  public
    constructor Create;
  end;

constructor EInternalError.Create;
begin
  inherited Create(
    '...' // your text goes here
  );
end;

那么你的代码就变成了:

function DoSomething(LI_Enum: TCustomEnum): Integer;
var
  lNumber : Integer;
begin
  case LI_Enum of
    ceValue1 : lNumber := 1;
    ceValue2 : lNumber := 2;
    ceValue3 : lNumber := 3;
  else
    raise EInternalError.Create;
  end;
  Result := 2 * lNumber;
end;