Альтернативы VBA короткого замыкания
VBA не замыкается на короткое замыкание
VBA не поддерживает короткое замыкание - по-видимому, потому, что он имеет только побитовые операции And/Or/Not и т.д. Из спецификации VBA языка: "Логические операторы - это простые операторы данных, которые выполняют побитовые вычисления в своих операндах". В этом свете имеет смысл, что VBA был спроектирован с true = &H1111
и false = &H0000
: таким образом логические операторы могут быть оценены как побитовые операции.
Отсутствие короткого замыкания может вызвать проблемы
-
Производительность: ReallyExpensiveFunction()
всегда будет выполняться, когда этот оператор будет оцениваться, даже если это не нужно по результату левой стороны условия
If IsNecessary() And ReallyExpensiveFunction() Then
'...
End If
-
Ошибки: если MyObj is Nothing, этот условный статус приведет к ошибке выполнения, поскольку VBA все равно попытается проверить значение Property
If Not MyObj Is Nothing And MyObj.Property = 5 Then
'...
End If
Решение, которое я использовал для реализации поведения с короткими ошибками, является вложенным If
s
If cond1 And cond2 Then
'...
End If
становится
If cond1 Then
If cond2 Then
'...
End If
End If
Таким образом, операторы If дают поведение с коротким замыканием, которое не мешает оценить cond2
, если cond1
- False
.
Если есть предложение Else, это создает повторяющиеся кодовые блоки
If Not MyObj Is Nothing And MyObj.Property = 5 Then
MsgBox "YAY"
Else
MsgBox "BOO"
End If
становится
If Not MyObj Is Nothing Then
If MyObj.Property = 5 Then
MsgBox "YAY"
Else
MsgBox "BOO" 'Duplicate
End If
Else
MsgBox "BOO" 'Duplicate
End If
Есть ли способ переписать выражения If
для сохранения поведения короткого замыкания, но избегать дублирования кода?
Возможно, с другим выражением ветвления типа Select Case
?
Чтобы добавить контекст к вопросу, вот конкретный случай, на который я смотрю. Я реализую хеш-таблицу, которая обрабатывает конфликты, связывая их в связанном списке. Размер базового массива принудительно исполняется в два раза, а хеши распределяются в текущий размер массива, обрезая их до соответствующей длины.
Например, предположим, что длина массива равна 16 (двоичная 10000). Если у меня есть ключ с хэшем до 27 (двоичный код 11011), я могу сохранить его в моем 16-сегментном массиве, сохранив только биты в пределах этого размера массива. Индекс, в котором будет храниться этот элемент, будет (hash value) And (length of array - 1)
, который в этом случае равен (binary 11011) And (1111)
, который равен 1011
, который равен 11. Фактический хэш-код сохраняется вместе с ключом в слоте.
При поиске элемента в хеш-таблице в цепочке необходимо проверить как хэш, так и ключ, чтобы определить, что найден правильный элемент. Однако, если хеш не совпадает, тогда нет причин проверять ключ. Я надеялся получить небольшое количество неосязаемого количества производительности, вложив Ifs в режим короткого замыкания:
While Not e Is Nothing
If keyhash = e.hash Then
If Key = e.Key Then
e.Value = Value
Exit Property
Else
Set e = e.nextEntry
End If
Else
Set e = e.nextEntry
End If
Wend
Вы можете видеть, что Set...
дублируется, и, следовательно, этот вопрос.
Ответы
Ответ 1
В качестве более общей оценки я предлагаю ввести флаги условий и использовать присвоение результатов сравнения для booleans:
dim cond1 as boolean
dim cond2 as boolean
cond1 = false
cond2 = false
' Step 1
cond1 = MyObj Is Nothing
' Step 2: do it only if step 1 was sucessful
if cond1 then
cond2 = MyObj.Property = 5
end if
' Final result:
if cond2 then
msgbox "Yay"
else
msgbox "Boo"
end if
Подвязывая эти флаги условий, каждый шаг безопасен, вы видите конечный результат в последнем флагом условия, и вы не делаете ненужных сравнений. И, для меня, он читается.
РЕДАКТИРОВАТЬ 14/09/07
Я обычно никогда не пропускаю блочные разделители, и поэтому я устанавливаю каждый оператор структур управления на новой строке. Но в этом случае вы можете тщательно получить очень плотную нотацию, напоминающую о коротком замыкании, также потому, что компилятор VBA инициирует переменные:
dim cond1 as boolean
dim cond2 as boolean
dim cond3 as boolean
dim cond4 as boolean
cond1 = MyObj Is Nothing
if cond1 then cond2 = MyObj.Property = 5
if cond2 then cond3 = MyObj.Property2 = constSomething
if cond3 then cond4 = not isNull(MyObj.Property77)
if cond4 then
msgbox "Hyper-Yay"
else
msgbox "Boo"
end if
Я мог бы согласиться на это. Это чистый поток для чтения.
Ответ 2
Есть способ. Тебе не обязательно понравится. Но это один из тех тщательно разработанных случаев, когда Goto
пригодится
If Not MyObj Is Nothing Then
If MyObj.Property = 5 Then
MsgBox "YAY"
Else
Goto JUMPHERE
End If
Else
JUMPHERE:
MsgBox "BOO" 'Duplicate
End If
Короткозамкнутый код для реализации короткого замыкания!
В качестве альтернативы, если вместо MsgBox "BOO"
- некоторый длинный и запутанный код, он может быть завернут в функцию и может быть записан дважды с минимальным воздействием/накладными расходами.
Что касается конкретного варианта использования, несколько операций Set
будут иметь минимальное влияние на производительность, и, следовательно, если вы хотите избежать использования Goto
(по-прежнему наиболее глобально эффективный подход, коразмер + производительность, избегая создания манекена переменные и т.д. - не имеет значения, хотя для такого небольшого фрагмента кода существует незначительный недостаток, просто повторяя команду.
Просто, чтобы проанализировать (ваш пример кода), сколько можно получить с помощью разных методов...
- Если оба условия верны:, есть 2 сравнения, 1 назначение, 0 скачков
- Если выполняется только первое условие: есть 2 сравнения, 1 назначение указателя, 1 прыжок
- Если выполняется только второе условие: существует 1 сравнение, 1 назначение указателя, 1 прыжок
- Если оба условия ложны: существует 1 сравнение, 1 назначение указателя, 1 прыжок (тот же, что и выше)
С точки зрения производительности, скачок обычно дороже сравнения (что происходит очень быстро в ALU против прыжка, что может привести к сбою в кеше кода, возможно, не в этих размерах, но все же прыжки дороги),
И нормальное присвоение по значению будет в лучшем случае столь же быстрым, как присвоение указателя или иногда хуже (это VBA, не может быть на 100% уверен в реализации p-кода)
Итак, в зависимости от вашего случая использования/ожидаемых данных вы можете попытаться свести к минимуму среднее число переходов на итерацию в вашем цикле и изменить порядок кода.
Ответ 3
Как насчет:
s = "BOO"
If Not MyObj Is Nothing Then
If MyObj.Property = 5 Then s = "YAY"
End If
MsgBox s