在 Delphi 中使用 TObjectDictionary 时如何避免 EInvalidPointer 错误?
How can I avoid EInvalidPointer error when using TObjectDictionary in Delphi?
程序通过window消息接收product information datas
。
在 TProductInstance.PutProductData
过程中处理的传入数据。
产品信息包含日期、名称、价格。
我想将数据存储为 TObjectDictionary
。键是产品的相同日期,值是产品信息数据列表作为 TObjectList。
我也想只维护最近 7 天的数据。
顺便说一下,当我从 TObjectDictionary 中删除项目进行维护时,会出现如下错误。
First chance exception at 214598.
Exception class EInvalidPointer with message 'Invalid pointer operation'. Process product.exe (3848).
这是由FProductDictionary.Remove(StringKey);
造成的。
如何避免 EInvalidPointer
维护最新 7 天数据的错误?
type
TProductItem = class(TObject)
private
FDate: String;
FName: String;
FPrice: Integer;
procedure SetDate(const value: String);
procedure SetName(const value: String);
procedure SetPrice(const value: Integer);
public
property Date: String read FDate write SetDate;
property Name: String read FName write SetName;
property Price: Integer read FPrice write SetPrice;
constructor Create(const date, name: String; const price: Integer);
end;
TProductItemList = class(TObjectList<TProductItem>);
type
TProductInstance = class(TObject)
private
public
FLatestDate: String;
FProductList: TProductItemList;
FProductDictionary: TObjectDictionary<String, TProductItemList>;
constructor Create;
destructor Destroy; override;
procedure PutProductData(var Data: LP_Data);
end;
implementation
constructor TProductInstance.Create;
begin
FLatestDate := '';
FProductList := TProductItemList.Create;
FProductDictionary := TObjectDictionary<String, TProductItemList>.Create([doOwnsValues]);
end;
procedure TProductInstance.PutProductData(var Data: LP_Data);
var
StringKey: String;
begin
if (Trim(LP_Data^.date) <> FLatestDate) then
begin
FProductDictionary.AddOrSetValue(Trim(LP_Data^.date), FProductList);
for StringKey in FProductDictionary.Keys do
begin
if (GetDateToInt(Trim(LP_Data^.date)) - GetDateToInt(FLatestDate) > 7) then
FProductDictionary.Remove(StringKey);
end;
FProductList.Free;
end;
FProductList.Add(TProductItem.Create(Trim(LP_Data^.date), Trim(LP_Data^.name), Trim(LP_Data^.price)));
FLatestDate := Trim(LP_Data^.date);
end;
已更新
type
TProductItem = class(TObject)
private
FDate: String;
FName: String;
FPrice: Integer;
procedure SetDate(const value: String);
procedure SetName(const value: String);
procedure SetPrice(const value: Integer);
public
property Date: String read FDate write SetDate;
property Name: String read FName write SetName;
property Price: Integer read FPrice write SetPrice;
constructor Create(const date, name: String; const price: Integer);
end;
type
TProductInstance = class(TObject)
private
public
FLatestDate: String;
FProductList: TObjectList<TProductItem>;
FProductDictionary: TObjectDictionary<String, TObjectList<TProductItem>>;
constructor Create;
destructor Destroy; override;
procedure PutProductData(var Data: LP_Data);
end;
implementation
constructor TProductInstance.Create;
var
LProductItem: TProductItem;
LProductItemList: TObjectList<TProductItem>;
LStringList: TStringList;
begin
FLatestDate := '';
FProductList := TObjectList<TProductItem>.Create;
FProductDictionary := TObjectDictionary<String, TObjectList<TProductItem>>.Create([doOwnsValues]);
end;
procedure TProductInstance.PutProductData(var Data: LP_Data);
var
StringKey: String;
begin
FProductList.Add(TProductItem.Create(Trim(LP_Data^.date), Trim(LP_Data^.name), Trim(LP_Data^.price)));
if (Trim(LP_Data^.date) <> FLatestDate) then
begin
LProductItemList := TObjectList<ProductItem>.Create;
for LProductItem in FProductList do
begin
LProductItemList.Add(LProductItem);
end;
FProductDictionary.AddOrSetValue(Trim(LP_Data^.date), LProductItemList);
FProductList.Clear;
LStringList := TStringList.Create;
for StringKey in FProductDictionary.Keys do
begin
if (GetDateToInt(Trim(LP_Data^.date)) - GetDateToInt(FLatestDate) > 7) then
begin
LStringList.Add(StringKey);
end;
end;
for StringKey in LStringList do
begin
FProductDictionary.Remove(StringKey);
end;
FreeAndNil(LStringList);
end;
end;
更新代码发生 EInvalidPointer
错误 FProductDictionary.Remove(StringKey);
我错了什么?
您提供的代码不完整。您没有显示 TProductInstance
的析构函数。对于这样的问题,您应该始终提供一个简单的 MCVE。这在单个控制台 .dpr 文件中很容易实现。
看看我们能看到的,很明显代码中的生命周期管理被破坏了。让我们批评一下这个方法。
procedure TProductInstance.PutProductData(var Data: LP_Data);
var
StringKey: String;
begin
if (Trim(LP_Data^.date) <> FLatestDate) then
begin
FProductDictionary.AddOrSetValue(Trim(LP_Data^.date), FProductList);
for StringKey in FProductDictionary.Keys do
begin
if (GetDateToInt(Trim(LP_Data^.date)) - GetDateToInt(FLatestDate) > 7) then
FProductDictionary.Remove(StringKey);
end;
FProductList.Free;
end;
FProductList.Add(TProductItem.Create(Trim(LP_Data^.date), Trim(LP_Data^.name),
Trim(LP_Data^.price)));
FLatestDate := Trim(LP_Data^.date);
end;
因为 FProductDictionary
拥有它的值,当您这样做时
FProductDictionary.AddOrSetValue(Trim(LP_Data^.date), FProductList);
然后 FProductDictionary
成为 FProductList
的所有者。这意味着你永远不应该破坏 FProductList
。但是,您确实这样做了:
FProductList.Free;
所以你会多次破坏 FProductList
这是一个明显的错误。
接下来要做什么?你需要处理生命周期的问题。我无法从此处提供的代码中了解您要实现的目标,以及应如何管理生命周期。你需要弄清楚谁负责拥有什么,并确保你坚持明确的生命周期管理政策。
从表面上看,我最好的猜测是您需要删除 FProductList
字段。当您需要向 FProductDictionary
添加新项目时,实例化 TProductItemList
的新实例,填充它,并将其添加到字典中。那时字典控制了 TProductItemList
.
的生命周期
作为最后一条评论,我建议 TProductItemList
类型毫无意义。我会删除它。使用 TObjectList<TProductItem>
使代码比 reader 更清晰。 reader 可以查看 TObjectList<TProductItem>
并立即知道它是什么,因为 TObjectList<T>
是一种无处不在的类型。
程序通过window消息接收product information datas
。
在 TProductInstance.PutProductData
过程中处理的传入数据。
产品信息包含日期、名称、价格。
我想将数据存储为 TObjectDictionary
。键是产品的相同日期,值是产品信息数据列表作为 TObjectList。
我也想只维护最近 7 天的数据。
顺便说一下,当我从 TObjectDictionary 中删除项目进行维护时,会出现如下错误。
First chance exception at 214598.
Exception class EInvalidPointer with message 'Invalid pointer operation'. Process product.exe (3848).
这是由FProductDictionary.Remove(StringKey);
造成的。
如何避免 EInvalidPointer
维护最新 7 天数据的错误?
type
TProductItem = class(TObject)
private
FDate: String;
FName: String;
FPrice: Integer;
procedure SetDate(const value: String);
procedure SetName(const value: String);
procedure SetPrice(const value: Integer);
public
property Date: String read FDate write SetDate;
property Name: String read FName write SetName;
property Price: Integer read FPrice write SetPrice;
constructor Create(const date, name: String; const price: Integer);
end;
TProductItemList = class(TObjectList<TProductItem>);
type
TProductInstance = class(TObject)
private
public
FLatestDate: String;
FProductList: TProductItemList;
FProductDictionary: TObjectDictionary<String, TProductItemList>;
constructor Create;
destructor Destroy; override;
procedure PutProductData(var Data: LP_Data);
end;
implementation
constructor TProductInstance.Create;
begin
FLatestDate := '';
FProductList := TProductItemList.Create;
FProductDictionary := TObjectDictionary<String, TProductItemList>.Create([doOwnsValues]);
end;
procedure TProductInstance.PutProductData(var Data: LP_Data);
var
StringKey: String;
begin
if (Trim(LP_Data^.date) <> FLatestDate) then
begin
FProductDictionary.AddOrSetValue(Trim(LP_Data^.date), FProductList);
for StringKey in FProductDictionary.Keys do
begin
if (GetDateToInt(Trim(LP_Data^.date)) - GetDateToInt(FLatestDate) > 7) then
FProductDictionary.Remove(StringKey);
end;
FProductList.Free;
end;
FProductList.Add(TProductItem.Create(Trim(LP_Data^.date), Trim(LP_Data^.name), Trim(LP_Data^.price)));
FLatestDate := Trim(LP_Data^.date);
end;
已更新
type
TProductItem = class(TObject)
private
FDate: String;
FName: String;
FPrice: Integer;
procedure SetDate(const value: String);
procedure SetName(const value: String);
procedure SetPrice(const value: Integer);
public
property Date: String read FDate write SetDate;
property Name: String read FName write SetName;
property Price: Integer read FPrice write SetPrice;
constructor Create(const date, name: String; const price: Integer);
end;
type
TProductInstance = class(TObject)
private
public
FLatestDate: String;
FProductList: TObjectList<TProductItem>;
FProductDictionary: TObjectDictionary<String, TObjectList<TProductItem>>;
constructor Create;
destructor Destroy; override;
procedure PutProductData(var Data: LP_Data);
end;
implementation
constructor TProductInstance.Create;
var
LProductItem: TProductItem;
LProductItemList: TObjectList<TProductItem>;
LStringList: TStringList;
begin
FLatestDate := '';
FProductList := TObjectList<TProductItem>.Create;
FProductDictionary := TObjectDictionary<String, TObjectList<TProductItem>>.Create([doOwnsValues]);
end;
procedure TProductInstance.PutProductData(var Data: LP_Data);
var
StringKey: String;
begin
FProductList.Add(TProductItem.Create(Trim(LP_Data^.date), Trim(LP_Data^.name), Trim(LP_Data^.price)));
if (Trim(LP_Data^.date) <> FLatestDate) then
begin
LProductItemList := TObjectList<ProductItem>.Create;
for LProductItem in FProductList do
begin
LProductItemList.Add(LProductItem);
end;
FProductDictionary.AddOrSetValue(Trim(LP_Data^.date), LProductItemList);
FProductList.Clear;
LStringList := TStringList.Create;
for StringKey in FProductDictionary.Keys do
begin
if (GetDateToInt(Trim(LP_Data^.date)) - GetDateToInt(FLatestDate) > 7) then
begin
LStringList.Add(StringKey);
end;
end;
for StringKey in LStringList do
begin
FProductDictionary.Remove(StringKey);
end;
FreeAndNil(LStringList);
end;
end;
更新代码发生 EInvalidPointer
错误 FProductDictionary.Remove(StringKey);
我错了什么?
您提供的代码不完整。您没有显示 TProductInstance
的析构函数。对于这样的问题,您应该始终提供一个简单的 MCVE。这在单个控制台 .dpr 文件中很容易实现。
看看我们能看到的,很明显代码中的生命周期管理被破坏了。让我们批评一下这个方法。
procedure TProductInstance.PutProductData(var Data: LP_Data);
var
StringKey: String;
begin
if (Trim(LP_Data^.date) <> FLatestDate) then
begin
FProductDictionary.AddOrSetValue(Trim(LP_Data^.date), FProductList);
for StringKey in FProductDictionary.Keys do
begin
if (GetDateToInt(Trim(LP_Data^.date)) - GetDateToInt(FLatestDate) > 7) then
FProductDictionary.Remove(StringKey);
end;
FProductList.Free;
end;
FProductList.Add(TProductItem.Create(Trim(LP_Data^.date), Trim(LP_Data^.name),
Trim(LP_Data^.price)));
FLatestDate := Trim(LP_Data^.date);
end;
因为 FProductDictionary
拥有它的值,当您这样做时
FProductDictionary.AddOrSetValue(Trim(LP_Data^.date), FProductList);
然后 FProductDictionary
成为 FProductList
的所有者。这意味着你永远不应该破坏 FProductList
。但是,您确实这样做了:
FProductList.Free;
所以你会多次破坏 FProductList
这是一个明显的错误。
接下来要做什么?你需要处理生命周期的问题。我无法从此处提供的代码中了解您要实现的目标,以及应如何管理生命周期。你需要弄清楚谁负责拥有什么,并确保你坚持明确的生命周期管理政策。
从表面上看,我最好的猜测是您需要删除 FProductList
字段。当您需要向 FProductDictionary
添加新项目时,实例化 TProductItemList
的新实例,填充它,并将其添加到字典中。那时字典控制了 TProductItemList
.
作为最后一条评论,我建议 TProductItemList
类型毫无意义。我会删除它。使用 TObjectList<TProductItem>
使代码比 reader 更清晰。 reader 可以查看 TObjectList<TProductItem>
并立即知道它是什么,因为 TObjectList<T>
是一种无处不在的类型。