Альтернативы 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