Дженерики не разрешают типы методов правильно

Рассмотрим следующее:

{$APPTYPE CONSOLE}

uses
  Generics.Collections;

type
  TObjProc = procedure of object;
  TFoo = class
    public procedure DoFoo;
    public procedure DoBar;
  end;

procedure TFoo.DoFoo;
begin
  WriteLn('foo');
end;

procedure TFoo.DoBar;
begin
  WriteLn('bar');
end;

var
  ProcList : TList<TObjProc>;
  Foo : TFoo;
  aProc : TObjProc;
begin
  Foo := TFoo.Create;
  ProcList := TList<TObjProc>.Create;
  ProcList.Add(Foo.DoFoo);
  ProcList.Add(Foo.DoBar);
  for aProc in ProcList do aProc;
  ReadLn;
end.

Это дает ожидаемый результат

foo
bar

Теперь предположим, что мы хотим назначить процедуру из списка. Перечисление работает, как указано выше. Это также работает:

aProc := ProcList.Items[0];
aProc;

Но это вызывает ошибку компилятора:

aProc := ProcList.First;
// E2010 Incompatible types: 
//'procedure, untyped pointer or untyped parameter' and 'TObjProc'

Что вдвойне нечетно, так как

function TList<T>.First: T;
begin
  Result := Items[0];
end;

Итак... что происходит?

Это влияет и на более новые версии Delphi? Я соблазн QC, если есть разумное ожидание, что это должно сработать (что, я думаю, есть).

Ответ 1

Это не ошибка компилятора, и эта проблема не связана с вашим использованием дженериков. Оба First и Last являются функциями, поэтому компилятор не может сказать, хотите ли вы их вызвать или ссылаться на них. Будьте ясны и дайте компилятору понять, что вы хотите вызвать функцию, поставив парсеры.

aProc := ProcList.First();
aProc := ProcList.Last();

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

Когда вы пишете ProcList.First, компилятор сталкивается с двусмысленностью. Вы хотите назвать функцию, или хотите ли вы обратиться к функции как процедурный? Во многих сценариях компилятор не может разрешить двусмысленность, но здесь это не так, где выражение найдено в правой части оператора присваивания. Столкнувшись с этой неоднозначностью, компилятор предполагает, что вы имеете в виду ссылку на функцию.

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

В качестве окончательного в стороне, если First и Last были реализованы как свойства, не было бы никакой двусмысленности.