Кто-нибудь знает, как вернуть число измерений переменной (Variant), переданной ей в VBA?
Как вернуть число измерений переменной (Variant), переданной ей в VBA
Ответ 1
Function getDimension(var As Variant) As Long
On Error GoTo Err
Dim i As Long
Dim tmp As Long
i = 0
Do While True
i = i + 1
tmp = UBound(var, i)
Loop
Err:
getDimension = i - 1
End Function
Это единственный способ, которым я мог придумать. Не очень & hellip;.
Глядя на MSDN, они в основном делали то же самое.
Ответ 2
@cularis и @Issun имеют вполне адекватные ответы на заданный вопрос. Однако я собираюсь задать вопрос. У вас действительно есть куча массивов неизвестного размера измерения, плавающего вокруг? Если вы работаете в Excel, единственной ситуацией, в которой это должно произойти, является UDF, где вы можете получить либо одномерный массив, либо двухмерный массив (или не массив), но ничего больше.
У вас почти никогда не должно быть рутины, которая ожидает что-то произвольное. И, таким образом, вы, вероятно, также не должны иметь общую "find # of array dimensions".
Итак, имея в виду, вот подпрограммы, которые я использую:
Global Const ERR_VBA_NONE& = 0
Global Const ERR_VBA_SUBSCRIPT_OUT_OF_RANGE& = 9
'Tests an array to see if it extends to a given dimension
Public Function arrHasDim(arr, dimNum As Long) As Boolean
Debug.Assert IsArray(arr)
Debug.Assert dimNum > 0
'Note that it is possible for a VBA array to have no dimensions (i.e.
''LBound' raises an error even on the first dimension). This happens
'with "unallocated" (borrowing Chip Pearson terminology; see
'http://www.cpearson.com/excel/VBAArrays.htm) dynamic arrays -
'essentially arrays that have been declared with 'Dim arr()' but never
'sized with 'ReDim', or arrays that have been deallocated with 'Erase'.
On Error Resume Next
Dim lb As Long
lb = LBound(arr, dimNum)
'No error (0) - array has given dimension
'Subscript out of range (9) - array doesn't have given dimension
arrHasDim = (Err.Number = ERR_VBA_NONE)
Debug.Assert (Err.Number = ERR_VBA_NONE Or Err.Number = ERR_VBA_SUBSCRIPT_OUT_OF_RANGE)
On Error GoTo 0
End Function
'"vect" = array of one and only one dimension
Public Function isVect(arg) As Boolean
If IsObject(arg) Then
Exit Function
End If
If Not IsArray(arg) Then
Exit Function
End If
If arrHasDim(arg, 1) Then
isVect = Not arrHasDim(arg, 2)
End If
End Function
'"mat" = array of two and only two dimensions
Public Function isMat(arg) As Boolean
If IsObject(arg) Then
Exit Function
End If
If Not IsArray(arg) Then
Exit Function
End If
If arrHasDim(arg, 2) Then
isMat = Not arrHasDim(arg, 3)
End If
End Function
Обратите внимание на ссылку на Chip Pearson отличный веб-сайт: http://www.cpearson.com/excel/VBAArrays.htm
Также см.: Как определить, инициализирован ли массив в VB6?. Мне лично не нравится недокументированное поведение, на которое он опирается, и производительность редко бывает такой важной в коде Excel VBA, который я пишу, но это тем не менее интересно.
Ответ 3
Для массивов MS имеет хороший метод, который включает в себя цикл до тех пор, пока не произойдет ошибка.
"Эта процедура проверяет массив с именем Xarray путем тестирования LBound каждого измерения. Используя цикл For... Next, процедура циклически проходит через число возможных размеров массива до 60000, пока не будет создана ошибка. обработчик ошибок принимает шаг счетчика, в котором петля не работает, вычитает один (поскольку предыдущий был последним без ошибки) и отображает результат в окне сообщения...."
http://support.microsoft.com/kb/152288
Очищенная версия кода (решила написать как функцию, а не суб):
Function NumberOfDimensions(ByVal vArray As Variant) As Long
Dim dimnum As Long
On Error GoTo FinalDimension
For dimnum = 1 To 60000
ErrorCheck = LBound(vArray, dimnum)
Next
FinalDimension:
NumberOfDimensions = dimnum - 1
End Function
Ответ 4
Чтобы вернуть количество измерений без ошибок при глотании:
Private Declare PtrSafe Sub memcpy Lib "kernel32" Alias "RtlMoveMemory" ( _
ByRef dest As Any, ByVal src As LongPtr, ByVal size As LongPtr)
Public Function GetDimensions(source As Variant) As Integer
Dim vt As Long, ptr As LongPtr
memcpy vt, VarPtr(source), 2 ' read the variant type (2 bytes) '
If (vt And &H2000) = 0 Then Exit Function ' return 0 if not an array '
memcpy ptr, VarPtr(source) + 8, Len(ptr) ' read the variant data at offset 8 '
If (vt And &H4000) Then memcpy ptr, ptr, Len(ptr) ' read by reference if the data is a reference '
If ptr Then memcpy GetDimensions, ptr, 2 ' read the number of dimensions at offset 0 (2 bytes) '
End Function
Использование:
Sub Examples()
Dim list1
Debug.Print GetDimensions(list1) ' >> 0 '
list1 = Array(1, 2, 3, 4)
Debug.Print GetDimensions(list1) ' >> 1 '
Dim list2()
Debug.Print GetDimensions(list2) ' >> 0 '
ReDim list2(2)
Debug.Print GetDimensions(list2) ' >> 1 '
ReDim list2(2, 2)
Debug.Print GetDimensions(list2) ' >> 2 '
End Sub
Ответ 5
Microsoft задокументировала структуру VARIANT и SAFEARRAY, и используя те, которые вы можете проанализировать двоичные данные, чтобы получить размеры.
Создайте нормальный модуль кода. Я называю свой "mdlDims". Вы бы использовали его, вызвав простую функцию "GetDims" и передав ей массив.
Option Compare Database
Option Explicit
Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Integer)
Private Declare Function VarPtrArray Lib "msvbvm60.dll" Alias "VarPtr" (var() As Any) As Long
'http://msdn.microsoft.com/en-us/library/windows/desktop/ms221482(v=vs.85).aspx
Private Type SAFEARRAY
cDims As Integer
fFeatures As Integer
cbElements As Long
cLocks As Long
pvData As Long
End Type
'Variants are all 16 bytes, but they are split up differently based on the contained type
'VBA doesn't have the ability to Union, so a Type is limited to representing one layout
'http://msdn.microsoft.com/en-us/library/windows/desktop/ms221627(v=vs.85).aspx
Private Type ARRAY_VARIANT
vt As Integer
wReserved1 As Integer
wReserved2 As Integer
wReserved3 As Integer
lpSAFEARRAY As Long
data(4) As Byte
End Type
'http://msdn.microsoft.com/en-us/library/windows/desktop/ms221170(v=vs.85).aspx
Private Enum VARENUM
VT_EMPTY = &H0
VT_NULL
VT_I2
VT_I4
VT_R4
VT_R8
VT_CY
VT_DATE
VT_BSTR
VT_DISPATCH
VT_ERROR
VT_BOOL
VT_VARIANT
VT_UNKNOWN
VT_DECIMAL
VT_I1 = &H10
VT_UI1
VT_UI2
VT_I8
VT_UI8
VT_INT
VT_VOID
VT_HRESULT
VT_PTR
VT_SAFEARRAY
VT_CARRAY
VT_USERDEFINED
VT_LPSTR
VT_LPWSTR
VT_RECORD = &H24
VT_INT_PTR
VT_UINT_PTR
VT_ARRAY = &H2000
VT_BYREF = &H4000
End Enum
Public Function GetDims(VarSafeArray As Variant) As Integer
Dim varArray As ARRAY_VARIANT
Dim lpSAFEARRAY As Long
Dim sArr As SAFEARRAY
'Inspect the Variant
CopyMemory VarPtr(varArray.vt), VarPtr(VarSafeArray), 16&
'If the Variant is pointing to an array...
If varArray.vt And (VARENUM.VT_ARRAY Or VARENUM.VT_BYREF) Then
'Get the pointer to the SAFEARRAY from the Variant
CopyMemory VarPtr(lpSAFEARRAY), varArray.lpSAFEARRAY, 4&
'If the pointer is not Null
If Not lpSAFEARRAY = 0 Then
'Read the array dimensions from the SAFEARRAY
CopyMemory VarPtr(sArr), lpSAFEARRAY, LenB(sArr)
'and return them
GetDims = sArr.cDims
Else
'The array is uninitialized
GetDims = 0
End If
Else
'Not an array, you could choose to raise an error here
GetDims = 0
End If
End Function
Ответ 6
Я предполагаю, что вы имеете в виду, не используя On Error Resume Next, который большинству программистов не нравится, а также означает, что во время отладки вы не можете использовать "Break On All Errors", чтобы остановить работу кода (Tools- > Options- > General → Error Trapping- > Break on All Errors).
Для меня одно решение заключается в том, чтобы похоронить Any On Error Resume Next в скомпилированную DLL, в старые времена это был бы VB6. Сегодня вы можете использовать VB.NET, но я предпочитаю использовать С#.
Если Visual Studio доступен для вас, то вот какой-то источник. Он вернет словарь, Dicitionary.Count вернет количество измерений. Элементы также будут содержать LBound и UBound в качестве конкатенированной строки. Я всегда запрашиваю массив не только для его размеров, но также для LBound и UBound этих измерений, поэтому я их объединяю и возвращаю весь набор информации в словаре сценариев
Вот источник С#, запустите библиотеку классов, вызывая его BuryVBAErrorsCS, установите ComVisible (true), добавьте ссылку на библиотеку COM "Время выполнения сценариев Microsoft", зарегистрируйтесь для Interop.
using Microsoft.VisualBasic;
using System;
using System.Runtime.InteropServices;
namespace BuryVBAErrorsCS
{
// Requires adding a reference to COM library Microsoft Scripting Runtime
// In AssemblyInfo.cs set ComVisible(true);
// In Build tab check 'Register for Interop'
public interface IDimensionsAndBounds
{
Scripting.Dictionary DimsAndBounds(Object v);
}
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IDimensionsAndBounds))]
public class CDimensionsAndBounds : IDimensionsAndBounds
{
public Scripting.Dictionary DimsAndBounds(Object v)
{
Scripting.Dictionary dicDimsAndBounds;
dicDimsAndBounds = new Scripting.Dictionary();
try
{
for (Int32 lDimensionLoop = 1; lDimensionLoop < 30; lDimensionLoop++)
{
long vLBound = Information.LBound((Array)v, lDimensionLoop);
long vUBound = Information.UBound((Array)v, lDimensionLoop);
string concat = (string)vLBound.ToString() + " " + (string)vUBound.ToString();
dicDimsAndBounds.Add(lDimensionLoop, concat);
}
}
catch (Exception)
{
}
return dicDimsAndBounds;
}
}
}
Для кода клиента VBA для Excel здесь есть источник
Sub TestCDimensionsAndBounds()
'* requires Tools->References->BuryVBAErrorsCS.tlb
Dim rng As Excel.Range
Set rng = ThisWorkbook.Worksheets.Item(1).Range("B4:c7")
Dim v As Variant
v = rng.Value2
Dim o As BuryVBAErrorsCS.CDimensionsAndBounds
Set o = New BuryVBAErrorsCS.CDimensionsAndBounds
Dim dic As Scripting.Dictionary
Set dic = o.DimsAndBounds(v)
Debug.Assert dic.Items()(0) = "1 4"
Debug.Assert dic.Items()(1) = "1 2"
Dim s(1 To 2, 2 To 3, 3 To 4, 4 To 5, 5 To 6)
Set dic = o.DimsAndBounds(s)
Debug.Assert dic.Items()(0) = "1 2"
Debug.Assert dic.Items()(1) = "2 3"
Debug.Assert dic.Items()(2) = "3 4"
Debug.Assert dic.Items()(3) = "4 5"
Debug.Assert dic.Items()(4) = "5 6"
Stop
End Sub
ПРИМЕЧАНИЕ WELL. Этот ответ обрабатывает варианты сетки, снятые с листа с Range.Value, а также массивы, созданные в коде с помощью Dim s (1) и т.д.! Некоторые из других ответов не делают этого.
Ответ 7
Function ArrayDimension(ByRef ArrayX As Variant) As Byte
Dim i As Integer, a As String, arDim As Byte
On Error Resume Next
i = 0
Do
a = CStr(ArrayX(0, i))
If Err.Number > 0 Then
arDim = i
On Error GoTo 0
Exit Do
Else
i = i + 1
End If
Loop
If arDim = 0 Then arDim = 1
ArrayDimension = arDim
End Function
Ответ 8
Как насчет использования ubound (var) + 1? Это должно дать вам последний элемент большинства переменных (если только это не пользовательский диапазон, но в этом случае вы должны знать эту информацию уже). Диапазон обычной переменной (например, при использовании функции split) начинается с 0; ubound дает вам последний элемент переменной. Так что, если у вас есть переменная с 8 элементами, например, она будет идти от 0 (lbound) до 7 (ubound), и вы можете узнать количество элементов, просто добавляя ubound (var) + 1. Например:
Public Sub PrintQntElements()
Dim str As String
Dim var As Variant
Dim i As Integer
str = "Element1!Element2!Element3!Element4!Element5!Element6!Element7!Element8"
var = Split(str, "!")
i = UBound(var) + 1
Debug.Print "First element: " & LBound(var)
Debug.Print "Last element: " & UBound(var)
Debug.Print "Quantity of elements: " & i
End Sub
Он выведет этот результат в окно Inmediate:
Первый элемент: 0
Последний элемент: 7
Количество элементов: 8
Кроме того, если вы не уверены, что первый элемент (lbound) равен 0, вы можете просто использовать:
i = UBound (var) - LBound (var) + 1
Ответ 9
Я нашел довольно простой способ проверить, возможно, нагруженный кучей кодирования faux pas, неправильным жаргоном и недобросовестными методами, но тем не менее:
Dim i as Long
Dim VarCount as Long
Dim Var as Variant
'generate your variant here
i = 0
VarCount = 0
recheck1:
If IsEmpty(Var(i)) = True Then GoTo VarCalc
i = i + 1
GoTo recheck1
VarCalc:
VarCount= i - 1
Примечание: VarCount, очевидно, вернет отрицательное число, если Var (0) не существует. VarCount - это максимальный ссылочный номер для использования с Var (i), я - количество вариантов, которые у вас есть.