VBA assigning new object to variable? - vba

I'm trying create new object from a module class in VBA, and I have a small diffcult. Two line of assigning code, look like the same, but result is different.
I got a error message:
After that, I switch to use (1) instead of (2), error was fixed.
But I dont understand; Why do they have this difference?

Dim declares a variable, Set instantiates it.
So, it's a good practice to always have Dim before Set.

If you do not use Dim to declare the specific type of a variable you may subsequently change the variable to another type, for example after;
set aosh = new AOSHRatioQuery
You could mutate the variable to a string;
aosh = "A pint of milk"
As the sendAsyncRequest method expects a AOSHRatioQuery as its 2nd argument & the VBA compiler knows that it cannot guarantee that the aosh variable will actually contain an instance of that type, type safety is violated & the Type Mismatch error is raised to prevent sendAsyncRequest from receiving garbage it cannot interpret.
Explicitly typing with Dim aosh as new AOSHRatioQuery tells the compiler that aosh is guaranteed to always be AOSHRatioQuery instance or Nothing (attempting to assign it to another type will raise an error) so it can be passed safely.

In VBA, you have to declare variables using the Dim keyword, and then defining their data types with the As keyword. That's just how its syntax works. As a general form:
Dim <variableName> As <dataType>

Related

How to handle object declaration in VBA (Error 91)

I'm stuck in VBA and I couldn't find a good answer in the other questions related to error 91. I want to create an object and store variables and arrays inside that object. I tried an approach like I would do in js:
Dim block As Object
...
Set block = Nothing
block.Name = "Unbekannter Prüfblock"
block.Awf = "Unbekannter Anwendungsfall"
block.Conditions = Array()
block.Checks = Array()
I use the "Set block = Nothing" because I will use it multiple times in a loop.
But all I get is error 91 - Object variable not set
How can I set the object?
Do I really have to declare everything in vba?
Isn't there a "stop annoying me with declaration notices" toggle? ;-)
Update
Thank you all so much for the detailed answers!
As suggested I created a class for "block" and also a class for "condition" and "check". Block for example:
Option Explicit
Public name As String
Public awf As String
Public conditions As Collection
Public checks As Collection
Then I use it inside my code like this:
Dim bl As Block
Dim co As Condition
Dim ce As Check
Set bl = New Block
bl.name = ws.Range("B" & i).value
bl.awf = ws.Range("B" & i).value
Set co = New Condition
co.attr = ws.Range("B" & i).value
co.value = ws.Range("C" & i).value
bl.conditions.Add co
VBA isn't Javascript; objects and their members cannot be created inline, they need a class definition.
When you make a member call against an object, you refer to it by name, and whenever that name refers to a null reference (Nothing) you'll get error 91.
To fix it, you need to ensure every member call is made against a valid object reference. Using the Set keyword you can assign such a reference, and to create a new instance of an object you can use the New keyword followed by the name of the class that defines the type you want a new instance of:
Dim Block As Object
Block.Something = 42 ' Error 91
Set Block = New SomeClass ' set reference
Block.Something = 42 ' OK
Note that because the object is declared As Object, every member call is late-bound (resolved at run-time); if the member doesn't exist (or if there's a typo), you'll get error 438 at run-time.
You can move this error to compile-time with early binding by using a more specific data type for the declaration:
Dim Block As SomeClass
Because the members of SomeClass are known at compile-time, the IDE will now provide you with a member completion list when you type up a member call, and typos will no longer be valid at compile-time: strive to remain in the early-bound realm whenever possible! Note: As Variant (explicit or not) is also similarly late-bound.
So we add a new class module and call it SomeClass and we add a couple of public fields:
Option Explicit
Public Name As String
Public Case As String
Public Condition As Variant
Public Check As Variant
And now you can create and consume a new instance of that class, and add instances of it to a collection to process later (note: you can't do that with a UDT/Type).
The VBIDE settings have an annoying option ("automatic syntax check", IIRC) that immediately pops a message box whenever there's a compilation error on the current line; uncheck it (invalid lines will appear in red, without a message box), but do have the "require variable declaration" setting checked: it will add Option Explicit to every module, and that will spare you from a number of easily avoidable run-time errors, moving them to compile-time.
In JS, you can add properties (together with values) on the fly to an object. That's not possible in VBA (and most other languages).
Your declaration Dim block As Object is defining a variable that is supposed to point to an Object. But it isn't pointing to anything yet, per default it is initialized with Nothing, which is, literally, nothing, and has neither properties nor methods, it's just a placeholder to signal "I don't point to anything yet". Furthermore, Object cannot be instantiated.
in VBA, you assign something to an object variable with Set (this is different to most other languages). If you want to create a new object, you use the keyword New.
However, before you do that, you need to know what kind of object (which class) you need. This can be an existing class, eg a Worksheet or a Range in Excel, or it can be an own defined class (by creating a new class module in your code). In any case, you need to define the properties and the methods of that class. Considering the most simple class module Class1 (of course you should think about a more usefull name):
Option Explicit
Public name as String
Public case as String
(I will not start to talk about private members and getter and setter).
You then write
Dim block As Class1
Set block = New Class1
block.name = "Unbekannter Prüfblock"
(But block.data = "Hello world" will not be possible as data is not a member of your class.)
Big advantage of this attempt is that the compiler can show you when you for example mistyped a property name before you even start your code (Debug->Compile). In JS, you will get either a runtime error or a new property on the fly - hard to find nasty bugs.
If you later need a new (empty) object, just create a new object using New. If the old object is not referenced anywhere else, the VBA runtime will take care that the memory is freed (so no need to write Set block = Nothing).
In case you really don't know the properties in advance (eg when you read XML or JSON files or a key-value list from an Excel sheet...), you can consider to use a Collection or a Dictionary. Plenty of examples on SO and elsewhere.
One remark to block.Condition = Array(). Arrays in VBA are not really dynamic, you cannot add or remove entries easily during runtime. In VBA, you have static and dynamic arrays:
Dim a(1 to 10) as String ' Static array with 10 members (at compile time)
Dim b() as String ' Dynamic array.
However, for dynamic members you need to define how many members you need before you write something into it, you use the ReDim statement for that. Useful if you can calculate the number of members in advance:
Redim b(1 to maxNames) ' Dynamic array, now with maxNames members (whatever maxNames is)
You can change the array size of a dynamic array with Redim Preserve, but that should be an exception (bad programming style, inperformant). Without Preserve, you will get a new array, but the former data is lost.
Redim Preserve b(1 to maxNames+10) ' Now with 10 more members.
If you really don't know the number of members and it can change often during runtime, again a Collection or a Dictionary can be the better alternative. Note that for example a Dictionary can itself a Dictionary as value, which allows to define Tree structures.
Regarding your issue adding to the collection:
You need to add this code to your class module "Block" - only then you can add objects to the collections
Private Sub Class_Initialize()
Set conditions = New Collection
set checks = new Collection
End Sub

Variable declaration placement guidelines in VBScript

Is there any rule for placement of variable declaration in VBScript, like if it should always be declared in the beginning? Or can I declare the variable while using it? Which one is more efficient?
Let's try with a simple code, with Option Explicit included so VBScript parser requests that all the variables used in the code are declared
Option Explicit
WScript.Echo TypeName( data )
WScript.Echo TypeName( MY_DATA )
Dim data : data = 10
Const MY_DATA = 10
WScript.Echo TypeName( data )
WScript.Echo TypeName( MY_DATA )
When executed it will ouptut
Empty
Integer
Integer
Integer
That is
The first access to data does not generate any error. Variable declaration (the Dim statement) is hoisted. If the variable is declared inside the same (or outer) scope where it will be used then there will not be any problem.
But the first output is Empty. Only the declaration is hoisted, not the value assignment that is not executed until the line containing it is reached.
That does not apply to constant declaration. Its value is replaced in code where it is used but the real declaration is delayed until the const line is reached (read here).
As long as the variables/constants can be reached (they are declared in the same or outer scope) it is irrelevant (to the VBScript parser/engine) where you place the declaration.
But, of course, you or others will have to maintain the code. Being able to put the variables anywhere doesn't mean you should do something like the previous code (please, don't). It is a lot easier to read/maintain the code if variable declaration is done before initialization/usage. The exact way of doing it just depends on coding style.

is it possible in vb.net to declare a variable as a string if it has already been declared as a date?

I am declaring this variable in my vb.net application:
Dim invoice_due_date As Date = get_invoice_due_date(InvoiceDate.Text, customer_sequence)
i want to have an if statement that will declare this variable again as a String rather than a Date
is this possible?
i tried putting the If below but it tells me it hides a variable in an enclosing block
Read up (google) on "variable scope". What you want is not only not possible, it would be bad practice.
You either declare a new variable (different name) with a different data type and load the contents of invoice_due_date into it or, as #Plutonix already suggested, use .ToString()

VBA: Run time error '91'?

All I'm trying to do here is save a reference to the currently active window, but it doesn't seem to be working. It gives me a run time error on the last line.
Dim SourceWindow As Window, QACheckWindow As Window
SourceWindow = ActiveWindow
I'm not exactly sure why. Isn't ActiveWindow supposed to return the currently active window? If not, how can I make a reference to it?
EDIT: The above is right at the beginning of my function, so all there is before it is Sub FuncName()
In VB object variables require the Set keyword to be assigned. Object properties that are objects also need to be Set. Runtime error 91 "object variable not set" is raised when the assignment doesn't use that keyword.
This is inherited from legacy Let keyword to assign values, and Set keyword to assign references; the Let eventually was deprecated (although still needed for defining properties) and the Set remained, leaving the VB6/VBA value assignment syntax like [Let] variable = value, where "Let" is optional.
In the declaration and assignment:
Dim SourceWindow As Window, QACheckWindow As Window
'this is like saying "Let SourceWindow = ActiveWindow":
SourceWindow = ActiveWindow
SourceWindow is an object, assigned as if it were a value - this causes VBA to attempt let-coercion through a default member call. If the object wasn't initialized, the member call fails with error 91. If the object was initialized but doesn't have a default member, error 438 is raised.
So in this case error 91 is being raised because of an implicit member call; the .net equivalent would be a NullReferenceException:
Dim SourceWindow As Window, Dim WindowTitle As String
'"SourceWindow" reference isn't set, the object can't be accessed yet:
WindowTitle = SourceWindow.Caption
I'm going to go a bit overboard here, but the legacy Let statement should not be confused with the Let clause (in VB.net) which, in the LINQ query syntax (in VB.net), computes a value and assigns it to a new, query-scoped variable (example taken from MSDN):
From p In products
Let Discount = p.UnitPrice*0.1 '"Discount" is only available within the query!
Where Discount >= 50
Select p.ProductName, p.UnitPrice, Discount
VB.net assigns both values and references, without the need to specify a Let or a Set, because in .net this distinction is a much thinner line, given how everything ultimately derives from System.Object... including System.ValueType. That's why the Set keyword was also deprecated in VB.net, and also why the VB.net syntax for defining properties has dropped the Let in favor of Set - because parameterless default members are illegal in VB.NET, so this ambiguous let-coercion doesn't happen.

object reference not set to an instance of object [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 2 years ago.
I have been getting an error in VB .Net
object reference not set to an instance of object.
Can you tell me what are the causes of this error ?
The object has not been initialized before use.
At the top of your code file type:
Option Strict On
Option Explicit On
Let's deconstruct the error message.
"object reference" means a variable you used in your code which referenced an object. The object variable could have been declared by you the or it you might just be using a variable declared inside another object.
"instance of object" Means that the object is blank (or in VB speak, "Nothing"). When you are dealing with object variables, you have to create an instance of that object before referencing it.
"not set to an " means that you tried to access an object, but there was nothing inside of it for the computer to access.
If you create a variable like
Dim aPerson as PersonClass
All you have done was tell the compiler that aPerson will represent a person, but not what person.
You can create a blank copy of the object by using the "New" keyword. For example
Dim aPerson as New PersonClass
If you want to be able to test to see if the object is "nothing" by
If aPerson Is Nothing Then
aPerson = New PersonClass
End If
Hope that helps!
sef,
If the problem is with Database return results, I presume it is in this scenario:
dsData = getSQLData(conn,sql, blah,blah....)
dt = dsData.Tables(0) 'Perhaps the obj ref not set is occurring here
To fix that:
dsData = getSQLData(conn,sql, blah,blah....)
If dsData.Tables.Count = 0 Then Exit Sub
dt = dsData.Tables(0) 'Perhaps the obj ref not set is occurring here
edit: added code formatting tags ...
In general, under the .NET runtime, such a thing happens whenever a variable that's unassigned or assigned the value Nothing (in VB.Net, null in C#) is dereferenced.
Option Strict On and Option Explicit On will help detect instances where this may occur, but it's possible to get a null/Nothing from another function call:
Dim someString As String = someFunctionReturningString();
If ( someString Is Nothing ) Then
Sysm.Console.WriteLine(someString.Length); // will throw the NullReferenceException
End If
and the NullReferenceException is the source of the "object reference not set to an instance of an object".
And if you think it's occuring when no data is returned from a database query then maybe you should test the result before doing an operation on it?
Dim result As String = SqlCommand.ExecuteScalar() 'just for scope'
If result Is Nothing OrElse IsDBNull(result) Then
'no result!'
End If
You can put a logging mechanism in your application so you can isolate the cause of the error. An Exception object has the StackTrace property which is a string that describes the contents of the call stack, with the most recent method call appearing first. By looking at it, you'll have more details on what might be causing the exception.
When working with databases, you can get this error when you try to get a value form a field or row which doesn't exist. i.e. if you're using datasets and you use:
Dim objDt as DataTable = objDs.Tables("tablename")
you get the object "reference not set to an instance of object" if tablename doesn't exists in the Dataset. The same for rows or fields in the datasets.
Well, Error is explaining itself. Since You haven't provided any code sample, we can only say somewhere in your code, you are using a Null object for some task. I got same Error for below code sample.
Dim cmd As IDbCommand
cmd.Parameters.Clear()
As You can see I am going to Clear a Null Object. For that, I'm getting Error
"object reference not set to an instance of an object"
Check your code for such code in your code. Since you haven't given code example we can't highlight the code :)
In case you have a class property , and multiple constructors, you must initialize the property in all constructors.