VB.Net: Initialising variables with the New constructor - vb.net

When initialising a list or a queue or a stack or anything similar, which method is preferred?
Dim q as Queue(Of Integer) = New Queue(Of Integer)
or
Dim q as New Queue(Of Integer)
Also, I've started to use New for string and integer variables - is this stupid? Is there any disadvantage to using New rather than just setting the variable to the default setting? E.g.
Dim Num1 As New Integer
Dim Str1 As New String("")
Dim Bool1 As New Boolean
Thank you!

If you'd ask programmers whether they like typing more or less when writing a program then the usual answer is "less!". If you ask them whether they like more or less bugs, the usual answer is "less!" Those are conflicting goals.
The As New syntax has been part of VB.NET for a very long time. It however does come with strings attached, you leave it entirely up to the runtime to figure out whether or not a new object should be created. That does tend to be a bug generator. Sometimes you really do want to create a new object, even though the variable is already assigned. It is also rather ambiguous, in this snippet for example:
For ix As Integer = 0 To 42
Dim q As New Queue(Of Integer)
'' etc...
Next
Question is: do you get one instance of the queue, created in the first pass of the loop or do you get 43 of them? What was actually intended by the programmer? It isn't very clear from the syntax.
There is a 3rd alternative that you overlooked and the one I prefer. Available since VB9 (aka VS2008) called "type inference". Where you don't specify the type of the variable but leave it up to the compiler to figure it out. This option needs to be turned on with Option Infer On, it is turned on by default. It combines the advantages of the abbreviated syntax that As New has and still lets you create the object explicitly in your code with the New statement:
Option Infer On
...
For ix As Integer = 0 To 42
Dim q = New Queue(Of Integer)
'' etc...
Next
Where q is inferred to be of type Queue by the compiler and it is crystal clear that the code generates 43 instances of the queue. The exact equivalent in the C# language is the var keyword.

Related

vb.net Source array type cannot be assigned to destination array type on Enum

I've had to update a vb.net project from .NetFramework 4 to .NetFramework 4.7.2. In the process the following code is now throwing an error
Dim actuatorModelsArr = DirectCast(retNumberList, Array) Dim
Dim actuatorModels = actuatorModelsArr.Cast(Of ACTUATORMODELS)().ToList()
The error is System.ArrayTypeMismatchException: Source array type cannot be assigned to destination array type.
retNumberList is an Integer array
ACTUATORMODELS is an Enum
in the .netFramework 4 version actuatorModels is a list of the Enum
{System.Collections.Generic.List`1[FisherIECLib.ACTUATORMODELS]}
The list is used later in the module via linq to grab one of the Enums as a return value.
Is there a way around this or a way to create a list of the Enum?
Thanks in advance,
Hank
I think it's interesting that it stopped working. You can replace the code with a Select and cast to make it work
Dim retNumberList = {1, 2, 3, 4}
' either of these will produce a List of your Enum
Dim a = retNumberList.Select(Function(i) DirectCast(i, ACTUATORMODELS)).ToList()
Dim b = retNumberList.Select(Function(i) CType(i, ACTUATORMODELS)).ToList()
As for the original not working:
Dim c = retNumberList.Cast(Of ACTUATORMODELS)().ToList()
The literature suggests this is the equivalent of a (type)obj c# style cast, but both versions of vb.net cast work in the select. I am not sure why.
djv's answer helps fix your problem. Hopefully this answer will explain what's going wrong. If you look at the reference source for Cast(Of T) on an untyped IEnumerable, you'll find that the first thing that happens is the C# equivalent of this:
Dim asTyped = TryCast(source, IEnumerable(Of TResult))
If asTyped IsNot Nothing Then Return asTyped
Surprisingly, this cast will work for Integer() to IEnumerable(Of ACTUATORMODELS). This sets the issue in motion, because when it comes to making a List(Of T) out of the resulting sequence, it turns out that Integer() and ACTUATORMODELS() are actually not interchangeable, but Cast has already treated the sequence as though they are.
Based on some testing, this issue seems to arise out of the interaction of this corner case with a corner case in how the List(Of T) range constructor works and the more general corner case of Integer() vs TEnum().
In the general case of iterating over Integer() as if it were IEnumerable(Of TEnum), it works. You can make a For Each loop over the sequence, the enumeration variable will have TEnum as the type, and you'll see the values as if they were TEnum.
The problem comes in the range constructor for List(Of T), and an optimization there for a source that implements ICollection(Of T). In that case, the ctor will try to use ICollection(Of T).CopyTo to copy the items into the list's internal storage. This is where the error ultimately occurs, because (going back to the implementation of Cast(Of T)) the source is still the Integer() array, and Array.Copy (via Array.CopyTo) is not OK with trying to do that with a destination of TEnum().
I feel like this is a bug somewhere, though I'm not sure if it's in Cast(Of T), the List(Of T) range ctor, or in the array copy handling. I'm also not sure it's something that will get fixed, since it's a corner case that hits what look like significant optimizations and would require a very specialized check to catch.

Visual basic variable is used before it has assigned value

this code is the same as others it is a random between 1 and 4 yet for some reason it says it is being used before it has a value it is the same code as 3 others that are the same but with different names yet this is happening can someone please help me?
Dim npc As Random
Dim ndamage As Integer
ndamage = npc.Next(1, 4)
If (Playerhealth.Value - ndamage) <= 0 Then
Playerhealth.Value = 0
Else
Playerhealth.Value = Playerhealth.Value - ndamage
End If
In the first three lines of code,
Dim npc As Random
Dim ndamage As Integer
ndamage = npc.Next(1, 4)
you declare npc and use it before it is assigned a value. You should use New to create a new instance:
Dim npc As New Random
Further Explanation
Random is a class, which means that its default value is Nothing (also called null in C#), so before it can be used it needs to be assigned a value. The easiest way in this case is to use New directly in the variable declaration line.
Random is a Class which provides a lot of methods to get different random numbers.
To access these methods you have to create an object (sometimes called instance) of that class.
This is done by the new operator. This operator will allocate new space on the heap (which is a memory area) and will fill it with objects values and references to methods and other objects.
If you skip the new statement, you program tries to access to not allocated memory. In several languages this will end up in an nullpointer exception, in vb.net you get an used before it has assigned value exception.
To solve your problem, create an object of the random class:
Dim npc As New Random

List contains duplicate Persons

Please see the code below:
Public Function ExecuteDynamicQuery(ByVal strSQL As String, ByVal list As List(Of clsType), ByVal tyType As clsType) As List(Of clsType) Implements IGenie.ExecuteDynamicQuery
Dim objParameterValues As New clsParameterValues
Dim iConnectionBLL As iConnectionBLL
Dim objCon As DbConnection
Dim objDR As DbDataReader
Dim paramValues() As DbParameter
objParameterValues = New clsParameterValues
iConnectionBLL = New clsConnectionBLL()
objCon = iConnectionBLL.getDatabaseTypeByDescription("Genie2")
Using objCon
paramValues = objParameterValues.getParameterValues
objDR = clsDatabaseHelper.ExecuteReader(objCon, CommandType.Text, strSQL, paramValues)
Do While objDR.Read
Dim tyType2 As clsType = tyType
tyType.PopulateDataReader(objDR)
list.Add(tyType2)
Loop
objDR.Close()
Return list
End Using
End Function
An SQL statement is passed to the function along with clsType (the base type). A list of types is returned e.g. a list of Persons. For example, in this case strSQL = "SELECT * FROM Persons". A list of 500 persons is returned but they are all the same person (the last person added to the list). I realise this is because the list is referncing the same object for each entry. How do I change this?
This is a situation where making the method generic would be useful. For instance:
Public Function MyGenericMethod(Of T As New)() As List(Of T)
Dim results As New List(Of T)()
For i As Integer = 0 To 9
Dim item As New T()
' Populate item ...
results.Add(item)
Next
Return results
End Function
For what it's worth, though, I see people trying do this kind of thing often, and it never sits well with me. I'm always the first one in line to suggest that common code should be encapsulated and not duplicated all over the place, but, I've never been convinced that creating some sort of data access layer that encapsulates the calls to ADO, but doesn't also encapsulate the SQL, is a good idea.
Consider for a moment that ADO, is in-and-of-itself an encapsulation of that part of the data-access layer. Sure, it can take a few more lines of code than you might like to execute a simple SQL command, but that extra complexity is there for a reason. It's necessary in order to support all of the features of the data source. If you try to simplify it, inevitably, you will one day need to use some other feature of the data source, but it won't be supported by your simplified interface. In my opinion, each data access method should use all of the necessary ADO objects directly rather than trying to some how create some common methods to do that. Yes, that does mean that many of your data access methods will be very similar in structure, but I think you'll be happier in the long run.
I've reduced your original code. The following sample is functionally equivalent to what you posted. Without knowing more about your types, it will hard to give you anything more than this, but maybe the reduction will make the code clear enough for you to spot a solution:
Public Function ExecuteDynamicQuery(ByVal sql As String, ByVal list As List(Of clsType), ByVal type As clsType) As List(Of clsType) Implements IGenie.ExecuteDynamicQuery
Dim paramValues() As DbParameter = New clsParameterValues().getParameterValues()
Using conn As DbConnection = iConnectionBLL.getDatabaseTypeByDescription("Genie2"), _
rdr As DbDataReader = clsDatabaseHelper.ExecuteReader(conn, CommandType.Text, sql, paramValues)
While rdr.Read()
type.PopulateDataReader(rdr)
list.Add(type)
End While
Return list
End Using
End Function
There are a few additional bits of advice I can give you:
You must have some way to accept parameter information for your query that is separate from the query itself. The ExecuteReader() method that you call supports this, but you only ever pass it an empty array. Fix this, or you will get hacked.
A implementation that uses Generics (as posted in another answer) would be much simpler and cleaner. The Genie interface you're relying doesn't seem to be adding much value. You'll likely do better starting over with a system that understands generics.
The problem of re-using the same object over and over can be fixed by creating a new object inside the loop. As written, the only way to do that is with a New clsType (and it seems you may have Option Strict Off, such that this could blow up on you at run time), through some messy reflection code, a switch to using generics as suggested in #2, or a by accepting a Func(Of clsType) delegate that can build the new object for you.

Why is VBA saying that it has found an 'ambiguous name'?

When compiling some code (declarations shown below) I get the error message 'Compile Error: Ambiguous name detected. SixTables'. I have looked here and elsewhere but cannot find anything that matches my problem. What appear to be the most common causes of this error, declaring two variables with identical names or giving the same name to a function and the sub that it is called from, do not apply. And yes, I know I could just change the name to something the system was happy with, but (1) I wouldn't learn what I'm doing wrong, and (2) I chose that name for a reason - it fits its purpose exactly :-)
Option Explicit
Dim ArmOfService As Byte
Dim CharacterNumber As Long
Dim CurrentTerm As Byte
Dim DecorationRollMade As Byte
Dim DecorationRollNeeded As Byte
Dim DiceSize As Byte
Dim GenAssignment
Dim GenAssignmentSwitchInt As Byte
Dim GenAssignmentSwitchOff As Byte
Dim iLoopControl
Dim jLoopControl
Dim kLoopControl
Dim LineIncrement As Integer
Dim lLoopControl
Dim Merc(100)
Dim NoOfDice As Byte
Dim OfficerPromotion(63 To 78) As Byte
Dim PromotionRollMade As Byte
Dim PromotionRollNeeded As Byte
Dim Roll As Byte
Dim SkillColumn
Dim SixTables
Dim SpecAssignmentSwitchEnd As Byte
Dim SurvivalRollMade As Byte
Dim SurvivalRollNeeded As Byte
Dim TechLevel As Byte
Dim Temp As Integer
Dim Term As Byte
Dim TestCount
Dim UnitAssignment
Dim WhichTable
Dim Year As Byte
EDIT: I'm so embarrassed that I can hardly bring myself to explain what the problem was. I knew I hadn't duplicated a name, since I was only using it once - as a function! Thanks all for your help, I'm now going to go and hide my face in shame...
From MSDN :
More than one object in the same scope may have elements with the
same name.
Module-level identifiers and project-level identifiers (module names
and referenced project names) may be reused in a procedure, although
it makes programs harder to maintain and debug. However, if you want
to refer to both items in the same procedure, the item having wider
scope must be qualified. For example, if MyID is declared at the
module level of MyModule , and then a procedure-levelvariable is
declared with the same name in the module, references to the
module-level variable must be appropriately qualified:
Dim MyID As String
Sub MySub
MyModule.MyID = "This is module-level variable"
Dim MyID As String
MyID = "This is the procedure-level variable"
Debug.Print MyID
Debug.Print MyModule.MyID
End Sub
An identifier declared at module-level conflicts with a procedure name.
For example, this error occurs if the variable MyID is declared at
module level, and then a procedure is defined with the same name:
Public MyID
Sub MyID
. . .
End Sub
Having had this issue many times, and not fully understanding why, I think there is an important fact that I have tested, and someone can confirm. It may be obvious to professional programmers, but I place this here for those that seek these answers on discussions, who are usually not professional programmers.
A variable declared within a sub() is not "declared" (memory assigned) until that sub() is executed. And when the execution of that sub() is complete, the memory is released. Until now, I thought Public variables acted in similar way; available to any module that used it, --BUT only existing as long as the module where they were declared was still executing.
However, for any Public variable who's declaration line is in any module in the workbook, that variable is declared and available at the execution of any module within the workbook, even if the module where the Public declaration exists is never called or executed.
Therefore, if you have a Public variable declaration in one module, and then again in a separate module that you plan to run independent of the first, Excel still sees 2 declarations for the same variable, and thus it is ambiguous.
To prove it, you can create a blank module within a workbook project, and add absolutely nothing except two Public declaration lines,
Public VariableX as String
Public VariableY as Integer
and those variables will be declared and available throughout the project, for any sub() executed. This fact is also probably the reason for the tip about minimizing the use of public variables.
Again, this may have been kindergarten level information for professional programmers, but most on this are not pros, and have to learn these lessons the hard way, or through discussion boards like this. I hope this helps someone.

How do I pass ItemSpec into GetBranchHistory()?

I'm trying to get information about specific branches in TFS, so to start, I'm trying to create a variable to assign as a BranchHistoryTreeItem. However, when I pass in the ItemSpec, I'm getting an error on Spec (not the definition, but where it's passed into GetBranchHistory):
Value of type 'Microsoft.TeamFoundation.VersionControl.Client.ItemSpec' cannot be converted to '1-dimensional array of Microsoft.TeamFoundation.VersionControl.Client.ItemSpec'
I understand the error, but I'm not entirely sure why it throwing it. Isn't this data type exactly what it's looking for? I believe I have the ItemSpec declared correctly, but I'm a bit lost here. Can anyone offer some advice on why I'm getting this? Code:
Sub GetBranchInfo()
Dim tfs As New TfsTeamProjectCollection(Common.BuildServerURI)
Dim Version = tfs.GetService(Of VersionControlServer)()
Dim Spec As New ItemSpec("$/Project1", RecursionType.None)
Dim BranchHistory As New BranchHistoryTreeItem(Version.GetBranchHistory(Spec, VersionSpec.Latest))
End Sub
GetBranchHistory takes an array of ItemSpecs.
My VB is a little rusty, but I think you want something like:
Dim Spec As New ItemSpec("$/Project1", RecursionType.None)
Dim Specs(1) = new ItemSpec() {Spec}
Dim BranchHistory As New BranchHistoryTreeItem(Version.GetBranchHistory(Specs, VersionSpec.Latest))