как безопасно обойти Delphi Error: «типы формальных и фактических параметров должны быть идентичны» - PullRequest
6 голосов
/ 12 января 2010

Мне нужен способ написать универсальную процедуру для воздействия на тип объекта или любого из его потомков.

Моей первой попыткой было объявить

procedure TotalDestroy(var obj:TMyObject);

но при использовании его с потомком

type TMyNewerObject = class(TMyObject);
var someNewerObject: TMyNewerObject;

TotalDestroy(someNewerObject);

Я получаю печально известную ошибку "типы формальных и фактических параметров должны быть идентичны"

Поэтому, пытаясь найти решение, я посмотрел на исходный код процедуры FreeAndNil системы Delphi. И я нашел это удивительное заявление, вместе с этим удивительным комментарием

{ FreeAndNil frees the given TObject instance and 
  sets the variable reference to nil.  
  Be careful to only pass TObjects to this routine. }

procedure FreeAndNil(var Obj);

Это позволяет избежать ошибки проверки типа, но не использует защитную сетку.

У меня вопрос ... есть ли какой-нибудь безопасный способ проверить тип нетипизированного параметра var?

или другими словами, можете ли вы улучшить этот исходный код Delphi, чтобы предупреждение не понадобилось?

procedure FreeAndNil(var Obj);
var
  Temp: TObject;
begin
  Temp := TObject(Obj);
  Pointer(Obj) := nil;
  Temp.Free;
end;

Ответы [ 3 ]

9 голосов
/ 12 января 2010

Давайте рассмотрим, что вы хотите сделать.

Вы хотите вызвать метод, который принимает X, передавая объект типа Y, где Y является потомком X. Замедление, параметр является параметром "var".

Давайте проанализируем, что вы могли бы сделать, если бы это было возможно.

type
    TBase = class
    end;
    TDescendant = class(TBase)
    end;

procedure Fiddle(var x: TBase);
begin
    x := TDescendant.Create;
end;

type
    TOtherDescendant = class(TBase)
    end;

var a: TOtherDescendant;
a := TOtherDescendant.Create;
Fiddle(a);

Э-э-э, теперь a больше не содержит экземпляр TOtherDescendant, он содержит экземпляр TDescendant. Это, вероятно, стало неожиданностью для кода, следующего за вызовом.

Вы должны учитывать не только то, что вы намереваетесь делать с предлагаемым вами синтаксисом, но и то, что вы могли бы делать с синтаксисом.

Вы должны прочитать отличный пост в блоге Эрика Липперта о похожих проблемах в .NET, который можно найти здесь: Почему параметры ref и out не допускают изменения типа? .

7 голосов
/ 13 января 2010

Я уже писал об этом раньше, используя пример, очень похожий на Lasse's :

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

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

Одна из причин, по которой нужно обходить ошибку, заключается в том, что вы пишете такую ​​функцию, как TApplication.CreateForm. Его задача - изменить значение входного параметра, а тип нового значения меняется и не может быть определен во время компиляции. Если вы пишете такую ​​функцию, то ваша единственная возможность в Delphi - использовать параметр нетипизированный var , и тогда для вызывающего и получающего получателя становится дополнительной нагрузкой уверен, что все идет хорошо. Вызывающая сторона должна убедиться, что она передает переменную, способную содержать значения любого типа, который будет помещена в нее функцией, а функция должна убедиться, что она хранит значение типа, совместимого с тем, что запрашивал вызывающий.

В случае CreateForm вызывающая сторона передает литерал ссылки на класс и переменную этого типа класса. Функция создает экземпляр класса и сохраняет ссылку в переменной.

Я не очень высоко ценю CreateForm или FreeAndNil, в основном из-за того, что их нетипизированные параметры жертвуют безопасностью типа в обмен на сравнительно небольшое дополнительное удобство. Вы не показали реализацию своей функции TotalDestroy, но я подозреваю, что ее параметр var в конечном итоге обеспечит ту же низкую полезность, что и в этих двух других функциях. Смотрите мои статьи на оба:

3 голосов
/ 12 января 2010

В дополнение к тому, что написал Лассе, что совершенно правильно, большую часть времени вы все равно не хотите передавать объект в параметр var .

Объект является ссылочным типом. То, что вы видите как объект, на самом деле является ссылкой на него. Вы хотели бы только передать объект ссылка в параметр var , если хотите заменить свой объект на новый объект. Если вы просто хотите иметь возможность изменять члены объекта, то вы можете сделать это, просто передав его обычному параметру. Вызов метода должен принимать параметр TMyObject вместо параметра var TMyObject, и он должен работать.

Конечно, если вы действительно заменяете объект, не стесняйтесь игнорировать все это и посмотрите ответ Лассе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...