Déterminer le type complet d'une variable
Par le de type complet d'une variable je veux dire le genre d'information que vous obtenez dans la fenêtre exécution:
Je tiens à déterminer le type d'informations de manière dynamique à l'aide de VBA. La fonction TypeName()
ne pas faire ce que je veux car il renvoie la sous-type d'une variante et ne fait pas la distinction entre, par exemple, une variable de type variant tenant un éventail, une variable objet la tenue d'une gamme, et une variable de portée tenant un éventail.
Comme une étape préliminaire, j'ai écrit une fonction qui détecte si une variante est passé. Il fonctionne par l'exploitation par référence à la sémantique. Le code fait les choses avec son argument qui ne peut être fait avec une variante et ainsi déclencher une erreur si la variable n'est pas en fait une variante:
Function IsVariant(var As Variant) As Boolean
Dim temp As Variant
Dim isVar As Boolean
If IsObject(var) Then
Set temp = var
Else
temp = var
End If
On Error Resume Next
Set var = New Collection
var = "test"
If Err.Number > 0 Then
isVar = False
Else
isVar = True
End If
On Error GoTo 0
If IsObject(temp) Then
Set var = temp
Else
var = temp
End If
IsVariant = isVar
End Function
Sur cette base, j'ai écrit:
Function FullType(var As Variant) As String
If IsVariant(var) Then
FullType = "Variant/" & TypeName(var)
Else
FullType = TypeName(var)
End If
End Function
Code de test:
Sub TestTypes()
Dim R As Range
Dim Ob As Object
Dim i As Integer
Dim v1 As Variant
Dim v2 As Variant
v1 = 10
i = 10
Set v2 = Range("A1")
Set Ob = Range("A2")
Set R = Range("A3")
Debug.Print "v1: " & FullType(v1)
Debug.Print "i: " & FullType(i)
Debug.Print "v2: " & FullType(v2)
Debug.Print "Ob: " & FullType(Ob)
Debug.Print "R: " & FullType(R)
End Sub
De sortie:
v1: Variant/Integer
i: Integer
v2: Variant/Range
Ob: Range
R: Range
C'est presque ce que je veux-mais ne fait pas la distinction entre une variable objet la tenue d'une gamme et une gamme variable tenant un éventail. J'ai essayé d'écrire une fonction appelée IsTypeObject
qui fonctionne de façon similaire à IsVariant
mais n'arrive pas à le faire fonctionner:
Function IsTypeObject(var As Variant) As Boolean
Dim temp As Variant
Dim isGeneric As Boolean
If (Not IsObject(var)) Or IsVariant(var) Then
IsTypeObject = False
Exit Function
End If
Set temp = var
On Error Resume Next
Set var = New Collection
Set var = ActiveWorkbook
If Err.Number > 0 Then
isGeneric = False
Else
isGeneric = True
End If
On Error GoTo 0
Set var = temp
IsTypeObject = isGeneric
End Function
Test:
Sub test()
Dim R As Range
Set R = Range("A1")
Debug.Print IsTypeObject(R)
End Sub
Mais cela s'imprime True
même si je pense que le même passé par référence sémantique qui rend IsVariant
travail devrait également faire IsTypeObject
de travail (vous ne pouvez pas affecter une collection d'une gamme). J'ai essayé différents réglages mais ne semble pas possible de distinguer entre le générique des variables d'objet et d'objet spécifique des variables comme variables de portée.
Donc -- des idées pour comment dynamiquement pour obtenir le plein type d'une variable? (La motivation est, dans le cadre d'un debug log utilitaire)
- La variante par les ouvrages de référence parce que le
VARIANT
struct a laVT_BYREF
drapeau qui peut ou peut ne pas être défini. Il n'y a rien de tel pour les non-variables de type Variant. Vous avez réflexion .NET, mais pas en VBA. - La chose étrange est que, même dans le cas de mon "succès"
IsVariant
, lorsque je passe unRange
variable et de regarder où l'erreur se pose en marchant à travers elle dans le débogueur, la ligneSet var = New Collection
ne fait pas déclencher l'erreur, la ligne suivante lorsque j'essaie d'assigner une chaîne, c'est quelles sont les causes de l'erreur que je suis de piégeage. Je pense que VBA doit mettre en œuvre passe par référence par l'intermédiaire d'une sorte de copier-méthode de restauration -- mais alors, il devient étrange que la ligne suivante provoque une erreur (par opposition à une incompatibilité de type de retour). - C'est parce que quand un objet est utilisé dans un contexte de valeurs, VB essaie d'utiliser le par défaut de la propriété de l'objet à la place. À condition que
var
est un objet,var = "test"
essaie d'assigner"test"
à la propriété par défaut devar
, et il n'en a pas (erreur 438, ce qui n'est pas une erreur d'incompatibilité de type). - Je pense que vous pouvez réécrire votre
IsVariant
fonction à être beaucoup plus sympathique, par manuellement l'analyse de lavt
membre de laVariant
. Il sera très probablement avoirVT_BYREF
drapeau et le drapeau quoi qu'il en fait des points d'. Mais afin de savoir si une variable a été déclarée dans le codeAs Range
ouAs Object
, vous devez être le compilateur. - Ce qui explique beaucoup de lui. La fenêtre variables locales probablement obtient ses informations par une combinaison de l'analyse des
Dim
états et dynamiquement via latype
fonction. Je me doutais bien qu'une certaine forme d'analyse est inévitable si je veux vraiment le type complet, mais a été en espérant que j'ai été donnant sur quelque chose (relativement) simple. - Je ne savais pas que vous pourriez explorer les types de variantes comme votre réponse à cette question. Merci pour ça. Si vous pourriez recueillir certains de vos commentaires dans une réponse, je vais l'accepter (ou au moins upvote si quelqu'un arrive avec une réponse plus complète).
- Question intéressante, d'un couple de points: dans votre
test()
routine vous utilisezIsObject
pasIsTypeObject
, mais cela ne fonctionne toujours pas. Bizarrement, vous pouvez obtenir ce travail à l'intérieur d'une seule fonction, mais dès que vous passez la variable à une autre fonction (même si vous spécifiezByRef
) est obtient une hiérarchie deVariant\Object\Range
comme indiqué par les habitants de la fenêtre elle-même. Bonne question mais, dommage, il semble que la réponse est non! - La solution peut impliquer l'accès à l'objet VBE modèle et l'analyse du code? Rubberduck le fait, analyse le projet dans son ensemble et résout les identifiants et leurs usages de tri et de détermine de quel type elles sont, mais ne pas essayer de déterminer le "type" d'une
Variant
- si nous avons légèrement modifié Rubberduck pour exposer une API COM que le code VBA peut utiliser pour "parcourir" ou "requête" une variable, la déclaration et les usages, je pense que vous pourriez avoir la dernière pièce de votre puzzle. FWIW, que de l'API COM est sur notre feuille de route. Bonne chance! - C'est une bonne idée, et il pourrait être nécessaire. C'est très probablement à ce que les habitants de la fenêtre de la réalité.
- Pas de. Les habitants de la fenêtre a effectivement accès à la pile d'appel. Ne sorte de la fenêtre d'observation. Ne pas dire qu'il n'est pas possible, juste que c'est pas comment les habitants de la fenêtre fonctionne sous le capot.
- Clairement, vous avez raison que la pile des appels est utilisé, mais je soupçonne qu'il y a probablement un élément de l'analyse ainsi.
Vous devez vous connecter pour publier un commentaire.
Oui, vous pouvez le faire: il nécessite un peu de connaissances sur les pointeurs et la notion de "déréférencement'...
Voici le code pour le faire:
(Déclarations pour les
CopyMemory
la fonction de l'API sont quelques paragraphes plus bas).Qui a besoin de quelques explications, parce que le Visual Basic famille de langues sont conçus pour vous protéger de la mise en œuvre des détails de variables et leurs types - et à partir de la notion de pointeurs en particulier - et mon code implique un peu de la pensée latérale.
En termes simples, les variables ont un nom - une chaîne de caractères comme 'intX" vous voyez dans votre code; une zone de mémoire allouée pour contenir les données réelles, et une adresse de mémoire.
Cette adresse sera effectivement pour le début de la mémoire allouée à la variable et la variable sera mis en œuvre comme une structure en mémoire définie par les décalages pour les données réelles, avec la taille (ou la longueur) de données - et, pour les types complexes, par des compensations pour des adresses à d'autres structures dans la mémoire. Ces tailles et les décalages sont prédéfinis: ils sont la mise en œuvre effective de la variable, et nous VBA développeurs ont rarement besoin de savoir à ce sujet - nous déclarer le type, et son tout fait pour nous.
La première chose que vous devez savoir aujourd'hui, c'est que les deux premiers octets à l'adresse d'une variable en VBA sont énumérés var type: c'est comment le VarType() fonctionne.
Lorsque le programme passe à cette adresse, au lieu de passer un copié répartition des données dans la mémoire, il passe à cette adresse comme un pointeur. Oui, je suis trop simplifier une partie de cela, mais VBA développeurs ne savent réellement la différence entre l'obtention d'un pointeur et d'une copie des données: c'est dans la
ByRef
etByVal
identifiants que nous utilisons pour la réception des paramètres lors de la déclaration d'une fonction.VBA et visual basic sont très, très bons pour un blindage de nous les détails: tellement bon, que nous ne pouvons pas utiliser
VarType
etTypeName
à détecter que nous avons adopté une valeur ou une référence à elle; ou même une référence à une référence, à une référence.Cela est important, car une variante est un wrapper pour les autres variables, et la structure vous donne un pointeur vers la variable qu'il contient avec le var type pour le décrire: cependant, nous n'avons aucun moyen de savoir que dans VBA - nous sont transmis directement à la ligne indiquée par l'adresse, tout le chemin vers les données que nous allons utiliser, et le VBA
varType
jamais nous dit que nous y sommes allés indirectement par une série de sauts à travers plusieurs adresses définies par des pointeurs.Cependant, que l'information n'existe pas, si vous êtes prêt à regarder ces deux octets derrière le pointeur à l'aide d'un appel d'API.
Comme je l'ai dit, ces deux octets contiennent le var type - mais il y a plus: ils contiennent de la var de type combiné avec un bit à bit marqueur
VT_BYREF
indiquant que c'est une référence ou un pointeur, pour le type de données stockées, et non les données elles-mêmes. Donc, ce code sera fiable de vous dire que votre var type, avec un peu de pensée latérale pour obtenir plus de VBA être utile lorsque nous avions plutôt qu'il n'a pas:À première vue, cette fonction semble être contre-productif: je suis de passage la variable de variante ou de type simple - par référence, donc il va toujours être combiné avec
VT_BYREF
. Et j'ai commenté le 'modulo' arithmétique de toute façon......Et voilà comment cela fonctionne: passer une variable simple, et il vous dira que vous avez passé la variable par référence:
...Et vous obtenez la sortie
vbString OR VT_BYREF
:Mais si vous passez par notre fonction d'une chaîne de variante, VBA mise en œuvre de la Variante va vous protéger de toute cette complexité sur les pointeurs et passage par référence - tout le chemin vers les données - et vous donner vos données avec toutes les informations inutiles sont supprimés:
...Et vous obtenez la sortie:
Je vais vous laisser le code d'un ou OU NON de l'opération sur les valeurs renvoyées avec
VT_BYREF
, pour vous donner la " Variante/' étiquette pour votre chaîne étendue des descripteurs de la Variante/Chaîne et de la Variante de/de Longues sorties.[Edit: en fait, c'est le sommet de la réponse, mis en œuvre dans
VariantTypeName
]Je vous recommande de déclarer l'appel CopyMemory API, comme illustré, avec des constantes de compilation conditionnelle pour tous les environnements que vous êtes susceptible de rencontrer:
Pendant ce temps, la plus difficile question - prise en Variante/Objet/Gamme - auront besoin de plus de travail. Je peux vous dire que votre variante contient une plage, et je peux dire que c'est une variante et non pas lui-même une gamme: mais je ne peux pas aller en bas de la chaîne de déclarations de révéler qu'un objet a été déclaré comme "objet" maintenant qu'il pointe vers une gamme:
Voici le code généré, et la sortie complète:
Résultats:
Dans l'ensemble, une question intéressante: la version courte de ma réponse, c'est que vous pouvez distinguer les variantes et les types simples, mais un objet déclaré que l '"objet" n'est pas prête à cette analyse.
VariantType
devrait êtreDereferencedType
.VariantTypeName()
fonction de vous donner le nom du type qu' "Variante/Long" ou "Long"Vous avez le code qui détermine si la variable est un
Variant
déjà. Maintenant tout ce que vous devez faire est d'obtenir le sous-type, non? Il y a une fonction intégrée pour exactement que: VarType.Il a des limites bien. Il fonctionne uniquement pour les types natifs. Elle renvoie toujours
vbUserDefinedType
(36) pour les types définis par l'utilisateur. Bien que, je suppose que vous pourriez étui avec un appel àTypeName
afin de terminer le travail hors tension.VarType
fait ce que je voulais depuis que dans le code que j'ai posté utiliséTypeName
pour les mêmes fins. Le point de la question, c'est qu'il n'était pas en mesure de distinguer un objet variable contenant une gamme et une gamme variable tenant un éventail. J'ai surtout essayer de reproduire le type d'informations que les habitants de la fenêtre affiche. Cette information doit être quelque part, mais seulement une partie de cette information peut être recueillie à partir de fonctions commeVarType
ouTypeName