1) разделение одним завсегдатаем форума всего программирования на ООП, олицетворением которого объявляются C++ и .net, и не-ООП, олицетворением которого объявляется QBasic;
2) вон тот мой код;
3) обсуждение делегатов с Хакером в ICQ.
* 1. Делегаты: крэш-курс
Делегат – это ссылка на конкретный метод конкретного объекта. Она состоит из двух частей: ссылки на объект и процедурного указателя. Вызов по делегату просто подставляет первую ссылку параметром Me при вызове по процедурному указателю.
Делегаты нисколько не ограничены миром .net: например, они есть в Delphi, где названы method pointers. Проблема с COM-делегатами в том, что в них должна быть сильная ссылка на объект; значит, сами COM-делегаты должны вести подсчёт ссылок на себя, и саморазрушаться строго тогда, когда необходимо. Проще всего добиться этого, реализовав делегаты в виде COM-объектов. Тогда даже синтаксис вызова метода через делегат окажется на 100% COM-совместимым, если реализовать его специфический “operator()” в виде функции по умолчанию (DISPID_VALUE).
Такие COM-объекты из одного члена–оператора() принято называть функторами. Так их и будем называть в дальнейшем, чтобы отвязаться от .нецкого неологизма и ненужных .нецких ассоциаций, сопровождающих его упоминание.
* 2. Мечты, мечты, где ваша сладость?
Представим себе, что в VB появилась поддержка функторов. Что оператор AddressOf для методов класса возвращает именно такой, как описано выше, функтор. Например: (совместимость использованного здесь синтаксиса с существующими языками не преследовалась)
- Код: Выделить всё
' MyObject – некий класс
' код класса MyObjects:
Private m_AllMyObjects() As MyObject
Public Delegate EnumMyObjectsProc(ByVal o As MyObject) As Boolean
Public Sub EnumMyObjects(ByVal callback As EnumMyObjectsProc)
Dim o As MyObject
For Each o In m_AllMyObjects
If Not callback(o) Then Exit Sub
Next
End Sub
' код класса AnotherClass:
Private m_AllMyObjects As MyObjects
Private m_AllObjectsValid As Boolean
Private m_ValidationParameters As Variant
Public Function ValidateObjects(ByVal Parameters As Variant) As Boolean
m_ValidationParameters = Parameters
m_AllObjectsValid = True
m_AllMyObjects.EnumMyObjects AddressOf ValidateObject
ValidateObjects = m_AllObjectsValid
End Function
Private Function ValidateObject(ByVal obj As MyObject) As Boolean
m_AllObjectsValid = obj.Validate(m_ValidationParameters)
ValidateObject = m_AllObjectsValid
End Function
Здесь предполагается, что в строке со словом AddressOf создастся экземпляр невидимого класса EnumMyObjectsProc, состоящего из одного безымянного метода Public Function [](ByVal o As MyObject) As Boolean и двух полей: m_Target As Object, m_TargetVTblOffset As Long. Контроль типов возлагается на компилятор, который будет гарантировать, что метод объекта m_Target, находящийся в его vtable по смещению m_TargetVTblOffset, действительно имеет подходящую сигнатуру. В следующей строчке, после выхода из MyObjects::EnumMyObjects, ссылка на функтор исчезнет, и он саморазрушится, освобождая хранящуюся в нём ссылку на экземпляр класса AnotherClass. При создании функтора не обязательно указывать метод своего объекта: можно написать и AddressOf SomeObject.AnySuitableMethod
До сих пор всё достаточно реалистично, правда?
* 3. Анонимные методы
Теперь вообразим себе конструкцию AddressOf Delegate … End Delegate, которая позволяет внутри одного метода определить другой метод (а внутри него, возможно, ещё методы) того же самого класса. Потом для внутреннего (безымянного) метода создаётся функтор, как обычно оператором AddressOf. Например, код класса AnotherClass перепишется следующим образом:
- Код: Выделить всё
Private m_AllMyObjects As MyObjects
Private m_AllObjectsValid As Boolean
Private m_ValidationParameters As Variant
Public Function ValidateObjects(ByVal Parameters As Variant) As Boolean
m_ValidationParameters = Parameters
m_AllObjectsValid = True
m_AllMyObjects.EnumMyObjects AddressOf Delegate(ByVal obj As MyObject) As Boolean
m_AllObjectsValid = obj.Validate(m_ValidationParameters)
Delegate = m_AllObjectsValid
End Delegate
ValidateObjects = m_AllObjectsValid
End Function
Уже компактнее, но ещё недостаточно круто. Возложим на компилятор ещё больше работы: пусть он найдёт внутри кода анонимного метода все ссылки на локальные переменные (включая параметры) того метода, внутри которого он определён, и добавит все эти переменные полями внутрь функтора, рядом со ссылкой на экземпляр AnotherClass, необходимой для доступа к членам этого класса. Теперь m_TargetVTblOffset внутри функтора уже не нужна: весь код анонимного метода будет скомпилирован в единственный метод функтора – потому что доступ к этому методу иначе, чем через функтор, всё равно невозможен (в отличие от предыдущего примера). И даже больше: если окажется, что код анонимного метода не ссылается ни на какие члены AnotherClass, то не нужно даже m_Target. В этом случае функтор сможет даже пережить смерть того объекта, из метода которого он был создан (в обычном случае ссылка изнутри функтора удерживает в живых такой объект даже тогда, когда прочих ссылок на этот объект нет).
Но просто добавить копии всех локальных переменных внутрь функтора мало: если анонимный метод поменяет значения каких-то из них, то внешний метод должен увидеть эти изменения. Сохранить внутри функтора ссылки на них тоже недостаточно: при возврате из внешнего метода все его локальные переменные уничтожатся, а экземпляры вложенных в него анонимных методов могут продолжать жить, если на них остались ссылки. Так что придётся весь блок локальных переменных, на которые ссылаются (“замыкаются”) вложенные методы, выделить в ещё один невидимый класс. При входе во внешнюю процедуру создастся экземпляр такого класса, в него скопируются все нужные локальные переменные, и весь доступ к ним изнутри метода будет идти через ссылку на этот невидимый объект. Все создаваемые внутри метода экземпляры анонимных функций тоже получат ссылку на этот объект. При выходе из внешнего метода он освободит свою ссылку; когда все созданные внутри него анонимные функции завершатся, они тоже освободят свои ссылки, и этот невидимый объект самоуничтожится. Таким образом, у функтора всегда оказывается максимум два поля: опциональное m_Target, если он ссылается на члены своего объекта, и либо m_TargetVTblOffset (для именованных методов), либо m_Context (для анонимных методов).
С использованием замыканий, AnotherClass упрощается донельзя:
- Код: Выделить всё
Private m_AllMyObjects As MyObjects
Public Function ValidateObjects(ByVal Parameters As Variant) As Boolean
ValidateObjects = True
m_AllMyObjects.EnumMyObjects AddressOf Delegate(ByVal obj As MyObject) As Boolean
ValidateObjects = obj.Validate(Parameters)
Delegate = ValidateObjects
End Delegate
End Function
Здесь в блок переменных, выделяемый в невидимый класс, попадут параметр Parameters и локальная переменная ValidateObjects. Однако, если бы внутри нашего анонимного метода был определён ещё один анонимный метод, то последний не смог бы ссылаться на локальную переменную Delegate своего внешнего метода, т.к. внутри него под этим именем была бы доступна собственная локальная переменная. Так что имеет смысл разрешить именовать вложенные методы:
- Код: Выделить всё
Private m_AllMyObjects As MyObjects
Public Function ValidateObjects(ByVal Parameters As Variant) As Boolean
ValidateObjects = True
m_AllMyObjects.EnumMyObjects AddressOf Delegate ValidateObject(ByVal obj As MyObject) As Boolean
ValidateObjects = obj.Validate(Parameters)
ValidateObject = ValidateObjects
End Delegate
End Function
Наличие имени у вложенного метода не позволяет его вызывать иначе, чем через функтор, и вообще не влияет ни на что, кроме доступности вложенным в него методам его локальной переменной, зарезервированной под возвращаемое значение. В частности, наличие имени у метода без возвращаемого значения не влияет вообще ни на что.
(Продолжение уже сочинено, но будет запощено отдельно, для поддержания интриги.)