Сегодня снова об особенностях анонимных функций.
Два предыдущих поста:
Вот код примера:
Вызов Unsubscribe в строке Writeln(Unsubscribe(HandlerProc)); вернет False. Причина впрочем прозаична, вызов метода HandlerProc заворачивается в анонимную функцию и каждый раз новую. Потому функция, которую подписали на событие, будет отличаться от функции, которую пытаемся отписать.
Казалось бы, простая задача - реализовать список с обработчиками и методами подписки и отписки превращается не в самую тривиальную.
Получается, что хранить обработчики лучше как TMethod, но если все же хотим уметь подписывать универсальные обработчики (анонимные и неанонимные), тогда придется для каждого типа писать свою логику сохранения в список и вызова обработчиков. Это будут либо overload методы, либо будем использовать Rtti и методы параметризированные типом.
А для преобразования анонимного метода в TMethod потребуется сочинять что то вроде того, что написано в Spring4D в Springs.Events.pas:
Два предыдущих поста:
- Просто баг в коде или особенность счетчика ссылок в замыканиях
- Немного про замыкание ( Одно замыкание на несколько анонимных функций в одном контексте)
У типа анонимный функций есть замечательная возможность. В качестве обработчика можно присваивать не только анонимные функции, но и обычные методы и статические функции. Казалось бы, нет причин почему не использовать тип reference to всегда.
Однако есть ситуации, когда reference to тип может сыграть злую шутку. И я сейчас не про вопрос производительности, о котором я думаю большинство осведомлено.
Проблема может возникнуть при передаче обычной процедуры или метода в параметр с анонимным типом. Покажем на примере.
Наиболее распространенный вариант использования процедурных типов в реализации подписки на события.
Пусть есть какой-нибудь список EventHandlers, и конечно же, как у правильных хипстеров он будет параметризован типом анонимной функций TList<TProc>.
Заведем два метода для возможности подписаться на событие и отписаться от него: Subscribe(AHandler: TProc) и Unsubscribe(AHandler: TProc): Boolean соответственно. Однако, крайне вероятно, вас постигнет неудача если в качестве обработчика вы используете неанонимную функцию. По крайне мере отписаться вы уже не сможете.
Вот код примера:
program ReferenceToProc; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Generics.Collections; var eventHandlers: TList<TProc>; procedure Subscribe(AHandler: TProc); begin eventHandlers.Add(AHandler); end; function Unsubscribe(AHandler: TProc): Boolean; begin Writeln(eventHandlers.IndexOf(AHandler)); // 0 Result := eventHandlers.Remove(AHandler) >= 0; end; procedure HandlerProc; begin Writeln('Вызов статической процедуры'); end; var proc: TProc; begin eventHandlers:= TList<TProc>.Create; proc := procedure() begin Writeln('Вызов анонимной функции') end; Subscribe(proc); // Подписываемся анонимной функцией Writeln(Unsubscribe(proc)); // Метод вернул True, Успех:) Subscribe(HandlerProc); // Подписываемся не анонимной функцией Writeln(Unsubscribe(HandlerProc)); // Метод вернет False Fail:( FreeAndNil(eventHandlers); readln; end.
Вызов Unsubscribe в строке Writeln(Unsubscribe(HandlerProc)); вернет False. Причина впрочем прозаична, вызов метода HandlerProc заворачивается в анонимную функцию и каждый раз новую. Потому функция, которую подписали на событие, будет отличаться от функции, которую пытаемся отписать.
Казалось бы, простая задача - реализовать список с обработчиками и методами подписки и отписки превращается не в самую тривиальную.
Получается, что хранить обработчики лучше как TMethod, но если все же хотим уметь подписывать универсальные обработчики (анонимные и неанонимные), тогда придется для каждого типа писать свою логику сохранения в список и вызова обработчиков. Это будут либо overload методы, либо будем использовать Rtti и методы параметризированные типом.
TEvent = class events: TList<TMethod>; class procedure Subscribe(AProc: TProc); overload; class procedure Subscribe(AProc: TProcedure); overload; class procedure Subscribe<T>(AProc: T); overload; end;
А для преобразования анонимного метода в TMethod потребуется сочинять что то вроде того, что написано в Spring4D в Springs.Events.pas:
procedure MethodReferenceToMethodPointer(const AMethodReference; const AMethodPointer); type TVtable = array[0..3] of Pointer; PVtable = ^TVtable; PPVtable = ^PVtable; begin // 3 is offset of Invoke, after QI, AddRef, Release PMethod(@AMethodPointer).Code := PPVtable(AMethodReference)^^[3]; PMethod(@AMethodPointer).Data := Pointer(AMethodReference); end; function TEvent.Cast(const handler): TMethod; begin if fTypeInfo.Kind = tkInterface then MethodReferenceToMethodPointer(handler, Result) else Result := PMethod(@handler)^; end;
Комментариев нет:
Отправить комментарий