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 个解决方案,但我都不喜欢其中任何一个。特别是有更多的变量或语句。有没有其他方法可以避免这种情况?
- 用
{$WARN USE_BEFORE_DEF OFF}
和 {$WARN USE_BEFORE_DEF ON}
包装函数
- 在每个 case 语句中使用
else Exit;
并在开头使用 Result := 0
- 初始化每个变量,尽管永远不会使用该值
通过执行以下操作
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;
每当我使用以下结构时,我仍然无法弄清楚如何消除有关未初始化变量的警告,即使我知道这永远不会发生。
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 个解决方案,但我都不喜欢其中任何一个。特别是有更多的变量或语句。有没有其他方法可以避免这种情况?
- 用
{$WARN USE_BEFORE_DEF OFF}
和{$WARN USE_BEFORE_DEF ON}
包装函数
- 在每个 case 语句中使用
else Exit;
并在开头使用Result := 0
- 初始化每个变量,尽管永远不会使用该值
通过执行以下操作
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;