什么时候选项类型中的Object被销毁了?

时间:2017-08-24 18:16:55

标签: delphi optional code-design

我一直在考虑使用Option类型。

这意味着转换如下函数:

Customer GetCustomerById(Int32 customerID) {...}

Customer c = GetCustomerById(619);
DoStuff(c.FirstName, c.LastName);

返回选项Maybe类型:

Maybe<Customer> GetCustomerById(Int32 customerID) {...}

在我的非功能语言中,我必须检查返回值是否存在:

Maybe<Customer> c = GetCustomerById(619);

if (c.HasValue)  
   DoStuff(c.Value.FirstName, c.Value.LastName);

这很有效:

  • 您从函数签名中知道是否可以返回null(而不是引发异常)
  • 在盲目使用
  • 之前,你会被检查检查返回的值

但是没有垃圾收集

但我没有使用其RAII的C#,Java或C ++。我在德尔福;具有手动内存管理的本机语言。我将继续以类似 C#的语言显示代码示例。

使用手动内存管理,我的原始代码:

Customer c = GetCustomerById(619);
if (c != nil) 
{
   try  
   {
      DoStuff(c.FirstName, c.LastName);
   }
   finally
   {
      c.Free();
   }
}   

转换为:

Maybe<Customer> c = GetCustomerById(619);
if (c.HasValue)
{
   try  
   {
      DoStuff(c.Value.FirstName, c.Value.LastName);
   }
   finally
   {
      c.Value.Free();
   }
}   

我现在有一个Maybe<>持有 无效 的引用;它的更糟比null更好,因为现在Maybe 认为它有有效内容,并且内容确实有一个指向内存的指针,但该内存不是& #39; t有效。

我已将NullReferenceException交易到随机数据损坏崩溃错误。

有没有人想过这个问题,以及解决它的技巧?

将Add。添加到Maybe

我考虑过在名为Free的结构中添加一个方法:

void Free()
{
   if (this.HasValue()) 
   {
      _hasValue = false;
      T oldValue = _value;
      _value = null;
      oldValue.Free();
   }  
}

如果人们称之为,哪个有效;并知道称之为;知道为什么叫它;并知道他们不应该打电话给他们。

许多微妙的知识,以避免我尝试使用选项类型时引入的危险错误。

当被Maybe<T>包裹的对象实际上通过一个名为canonical Free的方法间接销毁时,它也会崩溃:

Maybe<ListItem> item = GetTheListItem();
if item.HasValue then
begin
   DoStuffWithItem(item.Value);
   item.Value.Delete;
   //item still thinks it's valid, but is not
   item.Value.Selected := False;
end;

Bonus Chatter

Nullable / Maybe / Option类型在处理没有内置非值的类型时具有优势(例如记录,整数) ,字符串,其中没有内置的非值)。

如果某个函数返回一个不可为空的值,那么在没有使用某些特殊的 sentinal 值的情况下,无法传达不存在的返回结果。

function GetBirthDate(): TDateTime; //returns 0 if there is no birth date
function GetAge(): Cardinal; //returns 4294967295 if there is no age
function GetSpouseName: string; //returns empty string if there is no spouse name

选项用于避免特殊的标记值,并与调用者进行实际通信。

function GetBirthDate(): Maybe<TDateTime>;
function GetAge(): Maybe<Integer>;
function GetSpouseName: Maybe<string>;

不仅适用于非可空类型

Option类型通过将事物无关的东西分开来避免NullReferenceExceptions(或EAccessViolation地址$ 00000000)的流行即可。

函数返回特殊的,有时是危险的sentinal值

function GetBirthDate(): TDateTime; //returns 0 if there is no birth date
function GetAge(): Cardinal; //returns 4294967295 if there is no age
function GetSpouseName: string; //returns empty string if there is no spouse name
function GetCustomer: TCustomer; //returns nil if there is no customer

转换为不可能存在特殊危险的特殊价值的表格:

function GetBirthDate(): Maybe<TDateTime>;
function GetAge(): Maybe<Integer>;
function GetSpouseName: Maybe<string>;
function GetCustomer: Maybe<TCustomer>;

调用者意识到函数可以返回没有东西,并且他们必须通过检查存在的箍。对于已支持 null 的类型,Option使我们有机会尝试阻止人们导致NullReference异常。

在函数式编程语言中,它更加健壮;可以构造返回类型,以便不可能返回nil - 编译器只是不允许它。

在过程式编程语言中,我们所能做的最好就是锁定nil,并使其无法触及。在此过程中,调用者拥有更强大的代码。

有人可能会争论&#34;为什么不告诉开发人员永远不要犯错误&#34;

customer = GetCustomer();
Print(customer.FirstName);

不可

customer = GetCustomer();
if Assigned(customer)
   Print(customer.FirstName);

得到好的

问题是我希望编译器能够捕获这些错误。我希望错误首先更难发生。我想要一个成功的坑。它强制调用者理解该函数可能会失败。 签名本身解释了该做什么,并使处理它变得容易。

在这种情况下,我们隐式返回两个值:

  • 客户
  • 表示客户是否确实存在的标志

函数式编程语言中的人已经采用了这个概念,并且这是一个人们试图重新使用过程语言的概念,即如果值存在或不存在,你就会传达一种新的类型。试图盲目使用它会产生编译时错误:

customer = GetCustomer();
Print(customer.FirstName); //syntax error: Unknown property or method "FirstName"

奖金阅读

如果您想了解有关尝试在过程语言中使用功能Maybe monad的更多信息,可以参考更多有关该主题的想法:

2 个答案:

答案 0 :(得分:5)

唯一可以保护您免除Value Maybe Maybe中包含的任何内容的行为,就是从清除Maybe内容的 TMaybe<T> = record strict private FValue: T; public ... function ExtractValue: T; end; function TMaybe<T>.ExtractValue: T; begin if not _hasValue then raise Exception.Create('Invalid operation, Maybe type has no value'); Result := FValue; _hasValue = false; FValue := Default(T); end; 变量中提取值并使用结果就像你通常会使用任何对象引用一样。

类似的东西:

Maybe<ListItem> item = GetTheListItem();
if item.HasValue then
begin
   Value := item.ExtractValue;
   DoStuffWithItem(Value);
   Value.Free;
end;

然后你将被迫提取价值以便使用它。

1) yum install mysql-devel
2) gem install mysql2

答案 1 :(得分:3)

Delphi中不需要Maybe,因为对象是引用类型,因此您可以使用nil对象指针,例如:

type
  TCustomer = class
  public
    customerID: Int32;
    FirstName, LastName: string;
  end;

function GetCustomerById(customerID: Int32): TCustomer;
begin
  ...
  if (customerID is found) then
    Result := ...
  else
    Result := nil;
end;

var
  c: TCustomer;
begin
  c := GetCustomerById(619);
  if c <> nil then
    DoStuff(c.FirstName, c.LastName);
end;

如果函数需要为返回分配新对象,例如:

function GetCustomerById(customerID: Int32): TCustomer;
begin
  ...
  if (customerID is found) then
  begin
    Result := TCustomer.Create;
    ...
  end else
    Result := nil;
  ...
end;

然后你有两个终身管理选择(假设调用者需要获得对象的所有权,因为它不属于其他地方)。

1)完成使用对象后,您可以调用Free

var
  c: TCustomer;
begin
  c := GetCustomerById(619);
  if c <> nil then
  try
    DoStuff(c.FirstName, c.LastName);
  finally
    c.Free;
  end;
end;

2)您可以使用引用计数界面:

type
  ICustomer = interface
    ['{2FBD7349-340C-4A4E-AA72-F4AD964A35D2}']
    function getCustomerID: Int32;
    function getFirstName: string;
    function getLastName: string;
    property CustomerID: Int32 read getCustomerID;
    property FirstName: string read getFirstName;
    property LastName: string read getLastName;
  end;

  TCustomer = class(TInterfacedObject, ICustomer)
  public
    fCustomerID: Int32;
    fFirstName, fLastName: string;

    function getCustomerID: Int32;
    function getFirstName: string;
    function getLastName: string;
  end;

function TCustomer.getCustomerID: Int32;
begin
  Result := fCustomerID;
end;

function TCustomer.getFirstName: string;
begin
  Result := fFirstName;
end;

function TCustomer.getLastName: string;
begin
  Result := fLastName;
end;

function GetCustomerById(customerID: Int32): ICustomer;
begin
  ...
  if (customerID is found) then
  begin
    Result := TCustomer.Create as ICustomer;
    ...
  end else
    Result := nil;
end;

var
  c: ICustomer;
begin
  c := GetCustomerById(619);
  if c <> nil then
    DoStuff(c.FirstName, c.LastName);
end;