А как насчет того, когда Объект внутри типа параметра будет уничтожен?

Я смотрел на использование типа 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 (вместо того, чтобы поднимать исключение)
  • вы ткнули в проверенное возвращенное значение, прежде чем вслепую его использовать

Но нет сборки мусора

Но я не на C#, Java или C++ с его RAII. Я в Дельфи; родной язык с ручным управлением памятью. Я продолжу показывать примеры кода на 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<> держась за недействительную ссылку; это хуже, чем ноль, потому что теперь Maybe считает, что оно имеет допустимое содержимое, а содержимое имеет указатель на память, но эта память недействительна.

Я обменял возможное NullReferenceException к случайной ошибке повреждения данных.

Кто-нибудь задумывался над этой проблемой и методами ее решения?

Добавить

Я думал о добавлении метода в структуру под названием Free:

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

Что работает, если люди называют это; и знаю, как это назвать; знаю, почему это нужно назвать; и знаю, что они не должны звонить.

Много тонких знаний, чтобы избежать опасной ошибки, которую я представил только при попытке использовать тип опции.

Это также разваливается, когда объект, завернутый в Maybe<T> фактически уничтожается косвенно с помощью метода, не названного каноническим 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;

Бонус Болтовня

Nullable / Maybe / Option Тип имеет силу при работе с типами, которые не имеют встроенных не-значений (например, записей, целых чисел, строк, где нет встроенных не-значений).

Если функция возвращает ненулевое значение, то нет способа сообщить о отсутствии возвращаемого результата без использования некоторых специальных значений.

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), отделяя вещи без вещей.

Функции, возвращающие специальные, иногда опасные, значения часовых

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>;

Вызывающие люди понимают, что функция не может ничего возвратить, и они должны пройти через проверку наличия. В случае типов, которые уже поддерживают нулевое значение, Option дает нам возможность попытаться остановить людей от создания исключений NullReference.

В функциональных языках программирования это намного надежнее; тип возвращаемого значения может быть создан, поэтому невозможно вернуть nil - компилятор просто не допустит этого.

В процедурных языках программирования лучшее, что мы можем сделать, это заблокировать nil и сделать невозможным достичь. И в этом процессе вызывающая сторона имеет более надежный код.

Можно возразить: "Почему бы не сказать разработчику никогда не совершать ошибок":

Плохо

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 Монада на процедурных языках, вы можете проконсультироваться больше мыслей по этому вопросу:

2 ответа

Решение

Единственное, что может защитить вас от звонков на что-либо 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;

Вам не нужно Maybe в Delphi, поскольку объекты являются ссылочными типами, поэтому вы можете использовать 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;
Другие вопросы по тегам