Cannot use class object as argument in other class' method - vba

I'm new to VBA and was surprised it didn't have an official dynamic array, so I tried to make a simple one that suits my needs:
Public count As Integer
Private max As Integer
Private storage(1 To 5) As DVprogram
Private Sub class_initialize()
count = 0
max = 5
End Sub
Public Sub add(data As DVprogram)
If (count + 1) > max Then
updateSize (max + 5)
End If
storage(count + 1) = data
count = count + 1
End Sub
'more code...
When I try to call add, I get the error "Object doesn't support this property or method."
Dim test As New DVprogram
Dim temp As New progDynArray
temp.add (test)
When I change the array type to Integers, everything works fine, but when I try to use one of my own classes, it always throws this error. I've tried switching between ByVal and ByRef and neither had any affect. I also found this: Passing objects to procedures in VBA, but the solution there doesn't appear to be my problem.

You need to use Set when assigning objects:
Set storage(count + 1) = data
and (as noted by Gopi) you need to drop the () when calling your add method:
temp.add test
You don't use parentheses unless you're calling a function or using the Call keyword
EDIT: parentheses (without Call) don't always cause a problem - what they do is cause the argument you're passing to be first evaluated as an expression, and the result of that evaluation then get passed. In the case of updateSize (max + 5) that's not a problem, because that's the behavior you want anyway - to add 5 to max and pass that value to updateSize. In the case of temp.add (test) you don't want test to be evaluated as an expression.

Try temp.add test
instead of temp.add(test)

Related

Class variable as the counter of a For loop in VBA

I have a class module called MyClass, with a public integer in it:
Public i as Integer
When I try to use this variable in a For loop like so:
Dim MyInstance as MyClass: Set MyInstance = New MyClass
For MyInstance.i = 1 To 10
Debug.Print "Hello"
Next
I get the error: Variable required. Can't assign to this expression
I have consulted the help page but cannot see how it applies to my case. The relevant fragment is: "You tried to use a nonvariable as a loop counter in a For...Next construction. Use a variable as the counter." But i is a variable after all, and not a Let Property function or any other expression.
What is wrong with the code?
EDIT: I should point out that the reason I want my iterator to be part of the class is that I have multiple instances of the class, serving different purposes in my project, and there are multiple nested For loops for each instance of the class. Therefore it is worth having the iterators belong to their respective objects, say:
For Client.i = 1 To Client.Count
For Order.i = 1 To Order.Count
For Item.i = 1 To Item.Count
etc.
I have settled for the following workaround but am still not entirely satisfied with it:
For ciii = 1 To Client.Count
Client.i = ciii ' Client.i is later used in the code
For oiii = 1 To Order.Count
Order.i = oiii
For iiii = 1 To Item.Count
Item.i = iiii
You cannot use MyInstance.i as the increment counter but you can use it as the terminator; e.g. For i = 1 To MyInstance.i.
MyClass class
Option Explicit
Public pi As Long
Public Property Get i() As Long
i = pi
End Property
Public Property Let i(Value As Long)
pi = Value
End Property
test sub procedure in Module1
Sub test()
Dim MyInstance As MyClass, i As Long
Set MyInstance = New MyClass
MyInstance.i = 10
For i = 1 To MyInstance.i
Debug.Print "Hello"
Next
End Sub
If you want a publicly accessible loop variable stick it at the top of a standard module i.e. declare the Public i at the top of a standard module.
Note that this would mean you need to re-write your standard module code as, as per point two, you are treating i as if it is a property/method of the class.
So, standard module code would be:
Public i As Long
Sub ........
For i = 1 To 10
Debug.Print "Hello"
Next i
End Sub ......
If you want it to somehow be a property/method then you need to define Getters and Setters (potentially) in the class. And then re-write your module code accordingly. Especially if you are planning on looping using i, you will need an incrementor method in the class.
And yes, I have changed i to Long as there are no advantages, in this case I believe, of having it declared as Integer. A Long is a safer bet for avoiding potential overflow.
If you need a workaround so that you iterate through a property of the instance, you could create a method to increment it, change your loop to a Do While ... Loop and call that method before the loop call.
'Class Module
Option Explicit
Public i As Integer
Public Sub increment_i()
i = i + 1
End Sub
Private Sub Class_Initialize()
i = 0
End Sub
'Module
Sub loop_myclass()
Dim instance As MyClass: Set instance = New MyClass
Do While instance.i <= 10
'Instance property dependent code here
Debug.Print instance.i
instance.increment_i
Loop
End Sub
OK, I found the answer. There is a Microsoft help page on For…Next loop regarding VB, but I think it pertains to VBA as well.
It says:
If the scope of counter isn't local to the procedure, a compile-time
warning occurs.
So there's not much to discuss here, it's just the way MS wants it to be. Though I'd think that if the scope is greater than the procedure it shouldn't cause any problems, but apparently it does.

Initializing a static variable in VBA with non-default value

Static variables in VBA are simple enough:
Public Sub foo()
Static i As Integer
i = i + 1
Debug.Print i
End Sub
outputs (when called multiple times):
1
2
3
...
The problem is, VBA does not support initializing a variable on the same line as the declaration (not counting using : to put two lines on one):
Public Sub foo()
Dim i As Integer = 5 'won't compile!
Dim j As Integer
j = 5 'we have to do this instead
End Sub
This clashes with static variables:
Public Sub foo()
Static i As Integer 'we can't put an initial value here...
i = 5 'so this is how we'd usually initialize it, but...
i = i + 1
Debug.Print i
End Sub
You can probably see what happens - The very first thing the variable does every time foo is called is set itself back to 5. Output:
6
6
6
...
How can you initialize a static variable in VBA to a value other than its default? Or is this just VBA dropping the ball?
One way to do this if you want to keep the static semantics and not switch to a global is to sniff the default value and then set the initial condition:
Static i As Integer
if (i = 0) then i = 5
Safer alternative would perhaps be
Static i As Variant
if isempty(i) then i = 5
Or
Public Sub foo(optional init as boolean = false)
Static i As Integer
if init then
i = 5
exit sub
endif
You could probably also create a class with a default property and use class_initialize but that's probably a bit over-fussy.
I had the same issue in VB6, where it's exactly the same, and I like the Microsoft recommendation most:
Sub initstatic ()
Static Dummy, V, I As Integer, S As String
' The code in the following if statement will be executed only once:
If IsEmpty(Dummy) Then
' Initialize the dummy variant, so that IsEmpty returns FALSE for
' subsequent calls.
Dummy = 0
' Initialize the other static variables.
V = "Great"
I = 7
S = "Visual Basic"
End If
Print "V="; V, "I="; I, "S="; S
' Static variables will retain their values over calls when changed.
V = 7
I = I + 7
S = Right$(S, 5)
End Sub
I solved it as follows using a static boolean to indicate if you are entering the function for the first time. This logic should work for other situation as well, i think
Private Sub Validate_Date(TB as MSForms.TextBox)
Static Previous_Value as Date
Static Not_First_Time as Boolean
if Not_First_Time = False Then
Previous_Value = Now
Not_First_Time = True
endif
if IsDate(TB.Value) = False then TB.Value = Previous_Value
Previous_Value = TB.Value
End sub
The use of a Boolean to flag something as already initialized functions correctly in normal use but has an unexpected side effect when using the debugger. bIsInitialized is NOT reset to False when the VBA projuect is re-compiled. When the initialization code (or the constants the code uses) is changed changed, the thing being initialized will not be re-initialized while using the debugger.
One work-around is to set a breakpoint at the statement "If (Not bIsInitialized) Then" and add bIsInitialized as a watch variable. When the breakpoint is reached the first time, Click on the value and change it to false, remove the breakpoint and use F5 to continue. There may be a better work-around that uses something that is reliably reset by recompiling the project, but since the documentation for VBA says that the Boolean would be re-initialed after going out of context and all code stopping, there's no way to know if that behavior was version dependent. Not reinitializing the block of memory associated with the routine appears to be a performance optimization.
Static bIsInitialized as Boolean
Static something_time_consuming_to_initialize
If (Not bIsInitialized) Then
initialize something_time_consuming_to_initialize
bIsInitialized = True
End If

object required error on function call

I made a Class Module and named it Time. I wanted to have a function in it that created an instance of the Time object and returned it. See code below. If you can think of a better way to write the function, I'm all ears. The problem I'm having, as it stands is when I make the following statement:
tsheet.MondayStart = Time.Construct(Item.Value)
A debug.print statement prior to this call shows that Item.Value is "08:30".
tsheet is of type TimeSheet and MondayStart property is expecting to be assigned an object of type Time. However, when this statement executes at runtime, I get the object required error. I even tried removing the paranthesis, but all that does is bring up another error "Compile Error: Expected end of statement"
How do I fix this? Please advise. Too bad vba doesn't support the notion of construtors :-(
Alan
'IN TIME CLASS MODULE
Public Function Construct(Value As String) As Time
'This function expects a string formatted like: 08:30
'Time.Delimiter returns ":"
Dim tempTime As Time
Dim vhours As Integer
Dim vminutes As Integer
Dim arrTime() As Time
arrTime = Split(Value, Time.Delimiter)
hours = CInt(Trim(arrTime(0)))
minutes = CInt(Trim(arrTime(1)))
Set tempTime = New Time
tempTime.hours = vhours
tempTime.minutes = vminutes
Construct = tempTime
End Function
Actually I suggest you use the already implemented VBA methods DateValue and TimeValue which will accept a string and give you what you need.
I am not sure you need to re-invent the wheel here. Of course I might have missed something so please let me know.
Use the result of the new as follows:
Set MonStart = New TimeFrame
Set tsheet.MondayStart = MonStart.Initialize(MonStart, Item.Value)
(For comparision, here's previous:
Set MonStart = New TimeFrame
Set tsheet.MondayStart = TimeFrame.Initialize(MonStart, Item.Value)
)
Also use Set on function return value assignment.
for the first code example:
...
Set Construct = tempTime
End Function
and for the other code example:
...
Set Initialize = Value
End Function

VB.Net List.Find. Pass values to predicate

Having a bit of trouble using the List.Find with a custom predicate
i have a function that does this
private function test ()
Dim test As Integer = keys.Find(AddressOf FindByOldKeyAndName).NewKey
here's the function for the predicate
Private Shared Function FindByOldKeyAndName(ByVal k As KeyObj) As Boolean
If k.OldKey = currentKey.OldKey And k.KeyName = currentKey.KeyName Then
Return True
Else
Return False
End If
End Function
by doing it this way means i have to have a shared "currentKey" object in the class, and i know there has to be a way to pass in the values i'm interested in of CurrentKey (namely, keyname, and oldkey)
ideally i'd like to call it by something like
keys.Find(AddressOf FindByOldKeyAndName(Name,OldVal))
however when i do this i get compiler errors.
How do i call this method and pass in the values?
You can cleanly solve this with a lambda expression, available in VS2008 and up. A silly example:
Sub Main()
Dim lst As New List(Of Integer)
lst.Add(1)
lst.Add(2)
Dim toFind = 2
Dim found = lst.Find(Function(value As Integer) value = toFind)
Console.WriteLine(found)
Console.ReadLine()
End Sub
For earlier versions you'll have to make "currentKey" a private field of your class. Check my code in this thread for a cleaner solution.
I have an object that manages a list of Unique Property Types.
Example:
obj.AddProperty(new PropertyClass(PropertyTypeEnum.Location,value))
obj.AddProperty(new PropertyClass(PropertyTypeEnum.CallingCard,value))
obj.AddProperty(new PropertyClass(PropertyTypeEnum.CallingCard,value))
//throws exception because property of type CallingCard already exists
Here is some code to check if properties already exist
Public Sub AddProperty(ByVal prop As PropertyClass)
If Properties.Count < 50 Then
'Lets verify this property does not exist
Dim existingProperty As PropertyClass = _
Properties.Find(Function(value As PropertyClass)
Return value.PropertyType = prop.PropertyType
End Function)
'if it does not exist, add it otherwise throw exception
If existingProperty Is Nothing Then
Properties.Add(prop)
Else
Throw New DuplicatePropertyException("Duplicate Property: " + _
prop.PropertyType.ToString())
End If
End If
End Sub
I haven't needed to try this in newer versions of VB.Net which might have a nicer way, but in older versions the only way that I know of would be to have a shared member in your class to set with the value before the call.
There's various samples on the net of people creating small utility classes to wrap this up to make it a little nicer.
I've found a blog with a better "real world" context example, with good variable names.
The key bit of code to Find the object in the list is this:
' Instantiate a List(Of Invoice).
Dim invoiceList As New List(Of Invoice)
' Add some invoices to List(Of Invoice).
invoiceList.Add(New Invoice(1, DateTime.Now, 22))
invoiceList.Add(New Invoice(2, DateTime.Now.AddDays(10), 24))
invoiceList.Add(New Invoice(3, DateTime.Now.AddDays(30), 22))
invoiceList.Add(New Invoice(4, DateTime.Now.AddDays(60), 36))
' Use a Predicate(Of T) to find an invoice by its invoice number.
Dim invoiceNumber As Integer = 1
Dim foundInvoice = invoiceList.Find(Function(invoice) invoice.InvoiceNumber = invoiceNumber)
For more examples, including a date search, refer to Mike McIntyre's Blog Post

Hidden Features of VB.NET?

Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
I have learned quite a bit browsing through Hidden Features
of C# and was surprised when I couldn't find something
similar for VB.NET.
So what are some of its hidden or lesser known features?
The Exception When clause is largely unknown.
Consider this:
Public Sub Login(host as string, user as String, password as string, _
Optional bRetry as Boolean = False)
Try
ssh.Connect(host, user, password)
Catch ex as TimeoutException When Not bRetry
''//Try again, but only once.
Login(host, user, password, True)
Catch ex as TimeoutException
''//Log exception
End Try
End Sub
Custom Enums
One of the real hidden features of VB is the completionlist XML documentation tag that can be used to create own Enum-like types with extended functionality. This feature doesn't work in C#, though.
One example from a recent code of mine:
'
''' <completionlist cref="RuleTemplates"/>
Public Class Rule
Private ReadOnly m_Expression As String
Private ReadOnly m_Options As RegexOptions
Public Sub New(ByVal expression As String)
Me.New(expression, RegexOptions.None)
End Sub
Public Sub New(ByVal expression As String, ByVal options As RegexOptions)
m_Expression = expression
m_options = options
End Sub
Public ReadOnly Property Expression() As String
Get
Return m_Expression
End Get
End Property
Public ReadOnly Property Options() As RegexOptions
Get
Return m_Options
End Get
End Property
End Class
Public NotInheritable Class RuleTemplates
Public Shared ReadOnly Whitespace As New Rule("\s+")
Public Shared ReadOnly Identifier As New Rule("\w+")
Public Shared ReadOnly [String] As New Rule("""([^""]|"""")*""")
End Class
Now, when assigning a value to a variable declared as Rule, the IDE offers an IntelliSense list of possible values from RuleTemplates.
/EDIT:
Since this is a feature that relies on the IDE, it's hard to show how this looks when you use it but I'll just use a screenshot:
Completion list in action http://page.mi.fu-berlin.de/krudolph/stuff/completionlist.png
In fact, the IntelliSense is 100% identical to what you get when using an Enum.
Have you noticed the Like comparison operator?
Dim b As Boolean = "file.txt" Like "*.txt"
More from MSDN
Dim testCheck As Boolean
' The following statement returns True (does "F" satisfy "F"?)'
testCheck = "F" Like "F"
' The following statement returns False for Option Compare Binary'
' and True for Option Compare Text (does "F" satisfy "f"?)'
testCheck = "F" Like "f"
' The following statement returns False (does "F" satisfy "FFF"?)'
testCheck = "F" Like "FFF"
' The following statement returns True (does "aBBBa" have an "a" at the'
' beginning, an "a" at the end, and any number of characters in '
' between?)'
testCheck = "aBBBa" Like "a*a"
' The following statement returns True (does "F" occur in the set of'
' characters from "A" through "Z"?)'
testCheck = "F" Like "[A-Z]"
' The following statement returns False (does "F" NOT occur in the '
' set of characters from "A" through "Z"?)'
testCheck = "F" Like "[!A-Z]"
' The following statement returns True (does "a2a" begin and end with'
' an "a" and have any single-digit number in between?)'
testCheck = "a2a" Like "a#a"
' The following statement returns True (does "aM5b" begin with an "a",'
' followed by any character from the set "L" through "P", followed'
' by any single-digit number, and end with any character NOT in'
' the character set "c" through "e"?)'
testCheck = "aM5b" Like "a[L-P]#[!c-e]"
' The following statement returns True (does "BAT123khg" begin with a'
' "B", followed by any single character, followed by a "T", and end'
' with zero or more characters of any type?)'
testCheck = "BAT123khg" Like "B?T*"
' The following statement returns False (does "CAT123khg" begin with'
' a "B", followed by any single character, followed by a "T", and'
' end with zero or more characters of any type?)'
testCheck = "CAT123khg" Like "B?T*"
Typedefs
VB knows a primitive kind of typedef via Import aliases:
Imports S = System.String
Dim x As S = "Hello"
This is more useful when used in conjunction with generic types:
Imports StringPair = System.Collections.Generic.KeyValuePair(Of String, String)
Oh! and don't forget XML Literals.
Dim contact2 = _
<contact>
<name>Patrick Hines</name>
<%= From p In phoneNumbers2 _
Select <phone type=<%= p.Type %>><%= p.Number %></phone> _
%>
</contact>
Object initialization is in there too!
Dim x as New MyClass With {.Prop1 = foo, .Prop2 = bar}
DirectCast
DirectCast is a marvel. On the surface, it works similar to the CType operator in that it converts an object from one type into another. However, it works by a much stricter set of rules. CType's actual behaviour is therefore often opaque and it's not at all evident which kind of conversion is executed.
DirectCast only supports two distinct operations:
Unboxing of a value type, and
upcasting in the class hierarchy.
Any other cast will not work (e.g. trying to unbox an Integer to a Double) and will result in a compile time/runtime error (depending on the situation and what can be detected by static type checking). I therefore use DirectCast whenever possible, as this captures my intent best: depending on the situation, I either want to unbox a value of known type or perform an upcast. End of story.
Using CType, on the other hand, leaves the reader of the code wondering what the programmer really intended because it resolves to all kinds of different operations, including calling user-defined code.
Why is this a hidden feature? The VB team has published a guideline1 that discourages the use of DirectCast (even though it's actually faster!) in order to make the code more uniform. I argue that this is a bad guideline that should be reversed: Whenever possible, favour DirectCast over the more general CType operator. It makes the code much clearer. CType, on the other hand, should only be called if this is indeed intended, i.e. when a narrowing CType operator (cf. operator overloading) should be called.
1) I'm unable to come up with a link to the guideline but I've found Paul Vick's take on it (chief developer of the VB team):
In the real world, you're hardly ever going to notice the difference, so you might as well go with the more flexible conversion operators like CType, CInt, etc.
(EDIT by Zack: Learn more here: How should I cast in VB.NET?)
If conditional and coalesce operator
I don't know how hidden you'd call it, but the Iif([expression],[value if true],[value if false]) As Object function could count.
It's not so much hidden as deprecated! VB 9 has the If operator which is much better and works exactly as C#'s conditional and coalesce operator (depending on what you want):
Dim x = If(a = b, c, d)
Dim hello As String = Nothing
Dim y = If(hello, "World")
Edited to show another example:
This will work with If(), but cause an exception with IIf()
Dim x = If(b<>0,a/b,0)
This is a nice one. The Select Case statement within VB.Net is very powerful.
Sure there is the standard
Select Case Role
Case "Admin"
''//Do X
Case "Tester"
''//Do Y
Case "Developer"
''//Do Z
Case Else
''//Exception case
End Select
But there is more...
You can do ranges:
Select Case Amount
Case Is < 0
''//What!!
Case 0 To 15
Shipping = 2.0
Case 16 To 59
Shipping = 5.87
Case Is > 59
Shipping = 12.50
Case Else
Shipping = 9.99
End Select
And even more...
You can (although may not be a good idea) do boolean checks on multiple variables:
Select Case True
Case a = b
''//Do X
Case a = c
''//Do Y
Case b = c
''//Do Z
Case Else
''//Exception case
End Select
One major time saver I use all the time is the With keyword:
With ReallyLongClassName
.Property1 = Value1
.Property2 = Value2
...
End With
I just don't like typing more than I have to!
The best and easy CSV parser:
Microsoft.VisualBasic.FileIO.TextFieldParser
By adding a reference to Microsoft.VisualBasic, this can be used in any other .Net language, e.g. C#
AndAlso/OrElse logical operators
(EDIT: Learn more here: Should I always use the AndAlso and OrElse operators?)
Static members in methods.
For example:
Function CleanString(byval input As String) As String
Static pattern As New RegEx("...")
return pattern.Replace(input, "")
End Function
In the above function, the pattern regular expression will only ever be created once no matter how many times the function is called.
Another use is to keep an instance of "random" around:
Function GetNextRandom() As Integer
Static r As New Random(getSeed())
Return r.Next()
End Function
Also, this isn't the same as simply declaring it as a Shared member of the class; items declared this way are guaranteed to be thread-safe as well. It doesn't matter in this scenario since the expression will never change, but there are others where it might.
In vb there is a different between these operators:
/ is Double
\ is Integer ignoring the remainder
Sub Main()
Dim x = 9 / 5
Dim y = 9 \ 5
Console.WriteLine("item x of '{0}' equals to {1}", x.GetType.FullName, x)
Console.WriteLine("item y of '{0}' equals to {1}", y.GetType.FullName, y)
'Results:
'item x of 'System.Double' equals to 1.8
'item y of 'System.Int32' equals to 1
End Sub
I really like the "My" Namespace which was introduced in Visual Basic 2005. My is a shortcut to several groups of information and functionality. It provides quick and intuitive access to the following types of information:
My.Computer: Access to information related to the computer such as file system, network, devices, system information, etc. It provides access to a number of very important resources including My.Computer.Network, My.Computer.FileSystem, and My.Computer.Printers.
My.Application: Access to information related to the particular application such as name, version, current directory, etc.
My.User: Access to information related to the current authenticated user.
My.Resources: Access to resources used by the application residing in resource files in a strongly typed manner.
My.Settings: Access to configuration settings of the application in a strongly typed manner.
Custom Events
Though seldom useful, event handling can be heavily customized:
Public Class ApplePie
Private ReadOnly m_BakedEvent As New List(Of EventHandler)()
Custom Event Baked As EventHandler
AddHandler(ByVal value As EventHandler)
Console.WriteLine("Adding a new subscriber: {0}", value.Method)
m_BakedEvent.Add(value)
End AddHandler
RemoveHandler(ByVal value As EventHandler)
Console.WriteLine("Removing subscriber: {0}", value.Method)
m_BakedEvent.Remove(value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
Console.WriteLine("{0} is raising an event.", sender)
For Each ev In m_BakedEvent
ev.Invoke(sender, e)
Next
End RaiseEvent
End Event
Public Sub Bake()
''// 1. Add ingredients
''// 2. Stir
''// 3. Put into oven (heated, not pre-heated!)
''// 4. Bake
RaiseEvent Baked(Me, EventArgs.Empty)
''// 5. Digest
End Sub
End Class
This can then be tested in the following fashion:
Module Module1
Public Sub Foo(ByVal sender As Object, ByVal e As EventArgs)
Console.WriteLine("Hmm, freshly baked apple pie.")
End Sub
Sub Main()
Dim pie As New ApplePie()
AddHandler pie.Baked, AddressOf Foo
pie.Bake()
RemoveHandler pie.Baked, AddressOf Foo
End Sub
End Module
I just found an article talking about the "!" operator, also know as the "dictionary lookup operator". Here's an excerpt from the article at: http://panopticoncentral.net/articles/902.aspx
The technical name for the ! operator
is the "dictionary lookup operator." A
dictionary is any collection type that
is indexed by a key rather than a
number, just like the way that the
entries in an English dictionary are
indexed by the word you want the
definition of. The most common example
of a dictionary type is the
System.Collections.Hashtable, which
allows you to add (key, value) pairs
into the hashtable and then retrieve
values using the keys. For example,
the following code adds three entries
to a hashtable, and looks one of them
up using the key "Pork".
Dim Table As Hashtable = New Hashtable
Table("Orange") = "A fruit"
Table("Broccoli") = "A vegetable"
Table("Pork") = "A meat"
Console.WriteLine(Table("Pork"))
The ! operator can be used to look up
values from any dictionary type that
indexes its values using strings. The
identifier after the ! is used as the
key in the lookup operation. So the
above code could instead have been
written:
Dim Table As Hashtable = New Hashtable
Table!Orange = "A fruit"
Table!Broccoli = "A vegetable"
Table!Pork = "A meat"
Console.WriteLine(Table!Pork)
The second example is completely
equivalent to the first, but just
looks a lot nicer, at least to my
eyes. I find that there are a lot of
places where ! can be used, especially
when it comes to XML and the web,
where there are just tons of
collections that are indexed by
string. One unfortunate limitation is
that the thing following the ! still
has to be a valid identifier, so if
the string you want to use as a key
has some invalid identifier character
in it, you can't use the ! operator.
(You can't, for example, say
"Table!AB$CD = 5" because $ isn't
legal in identifiers.) In VB6 and
before, you could use brackets to
escape invalid identifiers (i.e.
"Table![AB$CD]"), but when we started
using brackets to escape keywords, we
lost the ability to do that. In most
cases, however, this isn't too much of
a limitation.
To get really technical, x!y works if
x has a default property that takes a
String or Object as a parameter. In
that case, x!y is changed into
x.DefaultProperty("y"). An interesting
side note is that there is a special
rule in the lexical grammar of the
language to make this all work. The !
character is also used as a type
character in the language, and type
characters are eaten before operators.
So without a special rule, x!y would
be scanned as "x! y" instead of "x !
y". Fortunately, since there is no
place in the language where two
identifiers in a row are valid, we
just introduced the rule that if the
next character after the ! is the
start of an identifier, we consider
the ! to be an operator and not a type
character.
This is built-in, and a definite advantage over C#. The ability to implement an interface Method without having to use the same name.
Such as:
Public Sub GetISCSIAdmInfo(ByRef xDoc As System.Xml.XmlDocument) Implements IUnix.GetISCSIInfo
End Sub
Forcing ByVal
In VB, if you wrap your arguments in an extra set of parentheses you can override the ByRef declaration of the method and turn it into a ByVal. For instance, the following code produces 4, 5, 5 instead of 4,5,6
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim R = 4
Trace.WriteLine(R)
Test(R)
Trace.WriteLine(R)
Test((R))
Trace.WriteLine(R)
End Sub
Private Sub Test(ByRef i As Integer)
i += 1
End Sub
See Argument Not Being Modified by Procedure Call - Underlying Variable
Passing parameters by name and, so reordering them
Sub MyFunc(Optional msg as String= "", Optional displayOrder As integer = 0)
'Do stuff
End function
Usage:
Module Module1
Sub Main()
MyFunc() 'No params specified
End Sub
End Module
Can also be called using the ":=" parameter specification in any order:
MyFunc(displayOrder:=10, msg:="mystring")
The Using statement is new as of VB 8, C# had it from the start. It calls dispose automagically for you.
E.g.
Using lockThis as New MyLocker(objToLock)
End Using
Import aliases are also largely unknown:
Import winf = System.Windows.Forms
''Later
Dim x as winf.Form
Consider the following event declaration
Public Event SomethingHappened As EventHandler
In C#, you can check for event subscribers by using the following syntax:
if(SomethingHappened != null)
{
...
}
However, the VB.NET compiler does not support this. It actually creates a hidden private member field which is not visible in IntelliSense:
If Not SomethingHappenedEvent Is Nothing OrElse SomethingHappenedEvent.GetInvocationList.Length = 0 Then
...
End If
More Information:
http://jelle.druyts.net/2003/05/09/BehindTheScenesOfEventsInVBNET.aspx
http://blogs.msdn.com/vbteam/archive/2009/09/25/testing-events-for-nothing-null-doug-rothaus.aspx
If you need a variable name to match that of a keyword, enclose it with brackets. Not nec. the best practice though - but it can be used wisely.
e.g.
Class CodeException
Public [Error] as String
''...
End Class
''later
Dim e as new CodeException
e.Error = "Invalid Syntax"
e.g. Example from comments(#Pondidum):
Class Timer
Public Sub Start()
''...
End Sub
Public Sub [Stop]()
''...
End Sub
There are a couple of answers about XML Literals, but not about this specific case:
You can use XML Literals to enclose string literals that would otherwise need to be escaped. String literals that contain double-quotes, for instance.
Instead of this:
Dim myString = _
"This string contains ""quotes"" and they're ugly."
You can do this:
Dim myString = _
<string>This string contains "quotes" and they're nice.</string>.Value
This is especially useful if you're testing a literal for CSV parsing:
Dim csvTestYuck = _
"""Smith"", ""Bob"", ""123 Anywhere St"", ""Los Angeles"", ""CA"""
Dim csvTestMuchBetter = _
<string>"Smith", "Bob", "123 Anywhere St", "Los Angeles", "CA"</string>.Value
(You don't have to use the <string> tag, of course; you can use any tag you like.)
DateTime can be initialized by surrounding your date with #
Dim independanceDay As DateTime = #7/4/1776#
You can also use type inference along with this syntax
Dim independanceDay = #7/4/1776#
That's a lot nicer than using the constructor
Dim independanceDay as DateTime = New DateTime(1776, 7, 4)
You can have 2 lines of code in just one line. hence:
Dim x As New Something : x.CallAMethod
Optional Parameters
Optionals are so much easier than creating a new overloads, such as :
Function CloseTheSystem(Optional ByVal msg AS String = "Shutting down the system...")
Console.Writeline(msg)
''//do stuff
End Function
Title Case in VB.Net can be achieved by an old VB6 fxn:
StrConv(stringToTitleCase, VbStrConv.ProperCase,0) ''0 is localeID
Properties with parameters
I have been doing some C# programming, and discovered a feature that was missing that VB.Net had, but was not mentioned here.
An example of how to do this (as well as the c# limitation) can be seen at: Using the typical get set properties in C#... with parameters
I have excerpted the code from that answer:
Private Shared m_Dictionary As IDictionary(Of String, Object) = _
New Dictionary(Of String, Object)
Public Shared Property DictionaryElement(ByVal Key As String) As Object
Get
If m_Dictionary.ContainsKey(Key) Then
Return m_Dictionary(Key)
Else
Return [String].Empty
End If
End Get
Set(ByVal value As Object)
If m_Dictionary.ContainsKey(Key) Then
m_Dictionary(Key) = value
Else
m_Dictionary.Add(Key, value)
End If
End Set
End Property