Как я могу назначить вариант варианту в VBA?

(Предупреждение: хотя на первый взгляд это может показаться таковым, это не вопрос для начинающих. Если вы знакомы с фразой "Допустить принуждение" или когда-либо изучали Спецификация VBA, пожалуйста, продолжайте читать.)

Допустим, у меня есть выражение типа Variant, и я хочу присвоить его переменной. Звучит легко, правда?

Dim v As Variant

v = SomeMethod()    ' SomeMethod has return type Variant

К сожалению, если SomeMethod возвращает объект (т. е. вариант с типом VarType для vbObject), разрешить приведение срабатывает, и v содержит «Простое значение данных» объекта. Другими словами, если SomeMethod возвращает ссылку на TextBox, v будет содержать строку.

Очевидно, решение состоит в том, чтобы использовать Set:

Dim v As Variant

Set v = SomeMethod()

К сожалению, это не работает, если SomeMethod не возвращает объект, например строка, приводящая к ошибке Несоответствие типов.

Пока единственное решение, которое я нашел, это:

Dim v As Variant

If IsObject(SomeMethod()) Then
    Set v = SomeMethod()
Else
    v = SomeMethod()
End If

который имеет неприятный побочный эффект двойного вызова SomeMethod.

Есть ли решение, которое не требует двойного вызова SomeMethod?


person Heinzi    schedule 02.03.2016    source источник
comment
Вы можете изменить SomeMethod на sub SomeMethod(var as variant): set var = xxx ... и назначить стиль v по ссылке с помощью SomeMethod v   -  person Alex K.    schedule 02.03.2016
comment
@AlexK.: По совпадению, это именно то, что я в итоге использовал, хотя и по другим причинам (мне нужно было что-то похожее на шаблон .NET TryParse/TryGet, поэтому я заставил SomeMethod вернуть логическое значение и использовать ByRef для варианта). Мне все еще нужно сделать эту уродливую проверку IsObject внутри SomeMethod, что меня немного беспокоит, но я думаю, что в VBA просто нет оператора LetSet v = ....   -  person Heinzi    schedule 03.03.2016


Ответы (3)


В VBA единственный способ присвоить Variant переменной, когда вы не знаете, является ли она объектом или примитивом, — это передать ее в качестве параметра.

Если вы не можете реорганизовать свой код так, чтобы v передавалось в качестве параметра свойству Sub, Function или Let (несмотря на Let, это также работает с объектами), вы всегда можете объявить v в области модуля и иметь выделенный Sub исключительно для цель сохранения-назначения этой переменной:

Private v As Variant

Private Sub SetV(ByVal var As Variant)
    If IsObject(var) Then
        Set v = var
    Else
        v = var
    End If
End Sub

где-то еще звонит SetV SomeMethod().

Некрасиво, но это единственный способ не вызывать SomeMethod() дважды и не трогать его внутреннюю работу.


Редактировать

Хорошо, я обдумал это и думаю, что нашел лучшее решение, которое ближе всего к тому, что вы имели в виду:

Public Sub LetSet(ByRef variable As Variant, ByVal value As Variant)
    If IsObject(value) Then
        Set variable = value
    Else
        variable = value
    End If
End Sub

[...] Я думаю, что в VBA просто нет оператора LetSet v = ...

Теперь есть: LetSet v, SomeMethod()

У вас нет возвращаемого значения, которое вам нужно Let или Set для переменной в зависимости от ее типа, вместо этого вы передаете переменную, которая должна содержать возвращаемое значение, в качестве первого параметра по ссылке, чтобы Sub может изменить свое значение.

person Leviathan    schedule 27.04.2016
comment
Другой синтаксис заключается в том, чтобы рассматривать LetSet как (параметрическое) свойство, что позволяет вам делать LetSet(v) = SomeMethod() - см. здесь для код - person Greedo; 12.11.2019

Вы можете использовать перехват ошибок, чтобы уменьшить ожидаемое количество вызовов методов. Сначала попробуй поставить. Если получится - не беда. В противном случае просто назначьте:

Public counter As Long

Function Ambiguous(b As Boolean) As Variant
    counter = counter + 1
    If b Then
        Set Ambiguous = ActiveSheet
    Else
        Ambiguous = 1
    End If
End Function

Sub test()
    Dim v As Variant
    Dim i As Long, b As Boolean

    Randomize
    counter = 0
    For i = 1 To 100
        b = Rnd() < 0.5
        On Error Resume Next
            Set v = Ambiguous(b)
            If Err.Number > 0 Then
                Err.Clear
                v = Ambiguous(b)
            End If
        On Error GoTo 0
    Next i
    Debug.Print counter / 100

End Sub

Когда я запустил код, в первый раз я получил 1,55, что меньше, чем 2,00, которые вы получили бы, если бы повторили эксперимент, но с заменой подхода обработки ошибок на наивный подход if-then-else, который вы обсуждали в своем вопросе.

Обратите внимание, что чем чаще функция возвращает объект, тем меньше вызовов функции в среднем. Если он почти всегда возвращает объект (например, это то, что он должен возвращать, но в некоторых случаях возвращает строку, описывающую состояние ошибки), то этот способ выполнения действий будет приближаться к 1 вызову на настройку/назначение переменной. С другой стороны - если он почти всегда возвращает примитивное значение, то вы приблизитесь к 2 вызовам на одно назначение - и в этом случае, возможно, вам следует реорганизовать свой код.

person John Coleman    schedule 01.05.2016

Похоже, я не t единственный с этой проблемой.

Решение было дано мне здесь.

Короче:

Public Declare Sub VariantCopy Lib "oleaut32.dll" (ByRef pvargDest As Variant, ByRef pvargSrc As Variant)
Sub Main()
  Dim v as Variant
  VariantCopy v, SomeMethod()
end sub

Кажется, это похоже на функцию LetSet(), описанную в ответе, но я решил, что это все равно будет полезно.

person Sancarn    schedule 24.04.2019
comment
Например, метод может возвращать определенный объект. Вам нужно передать вызов метода Variant, чтобы избежать сбоев: VariantCopy v, CVar(SomeMethod()) - person Cristian Buse; 12.07.2020
comment
@CristianBuse правда? Можете ли вы привести пример такого объекта? Я не сталкивался с этим раньше, но хотел бы узнать, что вызывает этот сбой :) - person Sancarn; 01.08.2020
comment
Извиняюсь за не правильное тестирование. Я использовал «Функция» вместо «Sub» для объявления VariantCopy API, и мне также не хватало возвращаемого типа (As Long), который ставит под угрозу API. Использование вашей декларации отлично работает. - person Cristian Buse; 02.08.2020