Can VB.NET specify the size of an array parameter? - arrays

I want to create a function that takes an argument of a reference to an array of at least a certain length, so that the function will know that there is enough space for it to write all the data it needs to write to the array.
Is this possible in VB.NET?
At the moment I am ReDim'ing the referenced array but I'm not sure if this is really working or not. (I guess I can test this method and break it by passing an array to small and see, will try that momentarily)
Public Function Foo(ByRef data() As Byte) As Boolean
If Data.Length < 4 Then
ReDim Preserve ProductId(4)
End If
' Other operations that put 4 bytes on the array...
Return True
End Function
Even if that method works, I'm not convinced that re-sizing the users array is really that great of an idea in comparison to just informing them that the parameter specifies a length of 4 somehow... Is there a better way of managing this?

Your function should take in a stream instead.
Public Function Foo(ByVal stream As Stream) As Boolean
'Write bytes to stream
End Function
For eaxmple you can call your method using a MemoryStream
Dim stream = new MemoryStream()
Foo(stream)
Dim array = stream.ToArray() 'Call ToArray to get an array from the stream.

No as far as I know you can't specify the size of the array in the parameters list.
You can however, check the size of the array like you are currently doing and then throw an ArgumentException. This seems to be one of the most common ways to validate data at the start of a method.

I'd actually try something similar to what you're doing here, but like this:
Public Function Foo(ByRef data() As Byte) As Boolean
If Data.Length < 4 Then
Return False
End If
'Other operations...
Return True
End Function
Or maybe this:
Public Function Foo(ByRef data() As Byte) As String
If Data.Length < 4 Then
Return "This function requires an array size of four"
End If
'Other operations...
Return "True"
End Function
Then, in your calling function, this:
Dim d As Boolean = Convert.ToBoolean(Foo(YourArray))
Then you can bubble up the error to the user, which is what you were looking to do, right?

Related

Searching a multi-dimensional array with VB.Net to count occurrences of a string

I have Dictionary defined as:
Private cdEmailsUploaded As New ConcurrentDictionary(Of String, Boolean)
The string contains the fullpath to an email file (MSG) and I want to create a function that returns a count of those that contain a given string in their path e.g "ProjectX".
I have assumed, possibly incorrectly, that it would be best to convert the Dictionary into an Array and then search it for the string and count the results. If there is a better way then please let me know.
The problem is that I can't get my head around the syntax to get the embedded function to search the string part of the array and not the Boolean.
What I have at the moment is:
Function QtyUploadedToLocation(sFind As String) As Integer
Dim emailsArr = cdEmailsUploaded.ToArray
Dim result As String() = Array.FindAll(emailsArr, Function(s) s.Key.StartsWith(sFind))
Return result.Length
End Function
The error I'm getting is:
Value of type 'KeyValuePair(Of String, Boolean)()' cannot be converted
to 'String()' because 'KeyValuePair(Of String, Boolean)' is not
derived from 'String'.
The signature of Array.FindAll is:
Public Shared Function FindAll(Of T) (array As T(), match As Predicate(Of T)) As T()
That means that for a dictionary type, it's working in KeyValuePair(Of TKey, TValue) throughout, and in particular, the result is an array of key/value pairs, not an array of string.
If you just want the count, you could a) fix the intermediate type, b) use type inference to avoid having to say what the type is (if you have Option Infer On), or c) just return the count directly, i.e.
Return Array.FindAll(emailsArr, Function(kvp) kvp.Key.StartsWith(sFind)).Length
If you want the array of strings for further processing, you can use the Linq Select function to get that sequence, i.e.
Dim matchingKeys = _
Array.FindAll(emailsArr, Function(kvp) kvp.Key.StartsWith(sFind)) _
.Select(Function(kvp) kvp.Key)
This will be an IEnumerable(Of String), you could use ToArray if you need it as an array or just leave it as-is if you want to do some other processing on it.

How do I return a value from 2 overloaded functions with different return types?

I created two functions to import (make) an array from a text file. They have the same function names but different number of parameters. They also have return values which are different since one importArray function is returning a 1D array and the other is returning a 2D array.
Overloads Function importArray(fileName As String) As Array
Overloads Function importArray(fileName As String, splitter As Char) As Array
Sub Main()
Dim getArray As New MakeArray
Dim printArray() As String = getArray.importArray("array.txt")
For i = 0 To printArray.Length - 1
'printArray
Next
Console.ReadKey()
End Sub
I can't seem to wrap my head around this. I can enter 2 parameters when calling the function or 1 then it's fine, but I don't know how I could specify which function to call, because when I am printing the array I don't know whether to use the 1D or 2D array. I can't do 2 for loops since using one dimension or 2 throws an error "Expression is not a method" so I'm not sure how I can get around this.
Is there a way I could determine whether I am using a 1D or 2D array by reading the text file? I wanted to keep the code as efficient as possible.
Thank you!
Firstly, don't use the Array type that way. If the method return String array, that should be the return type. If it returns a 2D String array then that should be the return type.
Overloads Function ImportArray(fileName As String) As String()
Overloads Function ImportArray(fileName As String, splitter As Char) As String(,)
When you call one of the functions, you assign it to a variable of the appropriate type for the method you're calling. Just think of them as two different methods. Then, you either use a single loop of two nested loops to traverse the data.
Dim arr1 As String() = getArray.ImportArray(fileName)
For i = 0 To arr1.GetUpperBound(0)
'...
Next
Dim arr2 As String(,) = getArray.ImportArray(fileName, splitter)
For i = 0 To arr2.GetUpperBound(0)
For j = 0 To arr2.GetUpperBound(1)
'...
Next
Next

Definitions of property procedures for the same property are inconsistent [using arrays]

I am having trouble with this pesky error message (see title)
I have seen it on a lot of posts on this site and others and it is usually some dumb mistake and try as I might I cannot see what dumb thing I am doing.
Public Property Get Contents() As Variant
Contents() = pContents()
End Property
Public Property Let Contents(Values() As Variant)
pContents = Values()
End Property
Public Property Get Content(Index As Integer) As String
Content = pContents(Index)
End Property
Public Property Let Content(Index As Integer, Value As String)
...
The aim being that the pair of get, let two allow the entire array to be read/written and the second allows read/writing to individual elements of the array by index.
According to this: https://msdn.microsoft.com/en-us/library/office/gg251357.aspx
Let and Get property statement thingies (Yes, I am fairly new to this) for a class in a class module have to meet certain requirements (which I am curious as to why exactly?). As far as I can tell, mine meet those requirements:
My Let has one more parameter (2) than my Get (1) - I'm assuming here that the "as string" outside of the brackets doesn't count as an argument.
Also I use the same types (integer and string) in both the Let and Get, I have no need for a Set so that shouldn't be a problem. I have also tried changing the names of the parameter variables to no avail.
So please help me, what is wrong and how can I avoid this mistake in the future?
Well I seem to have resolved the issue myself, posting the answer here in case it helps others! :)
Precautions to make when setting class properties to arrays (and reading them)
1) be careful with brackets(): don't use them in the Let statement
Public Property Let ArrayProperty (newArray as variant)
as opposed to the tempting
Public Property Let ArrayProperty (newArray() as variant)
Similarly, don't use them when using this property e.g:
Class1.ArrayProperty = myArray
no brackets after myArray
2) Check that your arrays are not empty in property (myArray) Let statements
Link here VBA: Don't go into loop when array is empty to the answer that helped me accomplish this, it seems you have to build your own function or use error handling to do it,
IsEmpty(myArray)
won't work.
and the code snippet adapted for my purposes (credit to original snippet goes to CreamyEgg):
Public Function IsEmptyArray(TestArray) As Boolean
Dim lngUboundTest As Long
lngUboundTest = -1
On Error Resume Next
lngUboundTest = UBound(TestArray)
On Error GoTo 0
If lngUboundTest >= 0 Then
IsEmptyArray = False
Else
IsEmptyArray = True
End If
End Function
3) remember that you might need to redim your property array to the Ubound(newArray), e.g.
Public Property Let Contents (newArray as variant)
'redim pArray to size of proposed array
ReDim pArray(1 To UBound(newArray))
'above line will throw an exception if newArray is empty
pArray = newArray
end Property
4) to deliberately make a class property an empty array, I used a temporary array variable, publicly declared outside of a sub at the start of a standard module
Public TempArray() As Variant
'outside of Sub ^^
Sub SetClass1ArrayToEmpty
Erase TempArray
class1.ArrayProperty = TempArray
End Sub
the erase method will make an array empty, i used it since I occasionally used TempArray to make arrays of size 1 and wanted to make sure it was empty
5) good news, setting a range to a class property seems to work the same way as setting a range to an array, I used application.transpose(myRange) to avoid problems with one column becoming a 2 dimensional array
class1.ArrayProperty = Application.Transpose(Range(myRange))
and there you have it, here is the class that works (no compile error)
Public Property Get Contents() As Variant
Contents() = pContents()
End Property
Public Property Let Contents(Values As Variant)
'checks for an empty array being passed to it first
If IsEmptyArray(Values) Then
Erase pContents
Else
'redim pContents to size of proposed array
ReDim pContents(1 To UBound(Values))
pContents = Values
End If
End Property
Public Property Get Content(Index As Integer) As String
Content = pContents(Index)
End Property
Public Property Let Content(Index As Integer, Value As String)
Select Case Index
Case Is < 0
'Error Handling
Case Is > UBound(pContents) + 1
'Error Handling
Case Is = UBound(pContents) + 1 'append to end of array
ReDim Preserve pContents(UBound(pContents) + 1)
pContents(Index) = Value
Case Else 'replace some middle part of array
pContents(Index) = Value
End Select
End Property
Hope this helps some peeps!

Which function for finding if a string is in an array (VBA) is better?

I have 2 functions that check to see if a string exists in an array.
I don't know which is better and if there are any reasons to use one over the other. Any help would be appreciated. Thank you :)
function 1
Function IsInArray(stringToBeFound As String, arr As Variant) As Boolean
IsInArray = (UBound(Filter(arr, stringToBeFound)) > -1)
End Function
function 2
Function IsInArray(myArray As Variant, val As String) As Boolean
Dim i As Integer, found As Boolean
found = False
If Not Len(Join(myArray)) > 0 Then
found = False
Else
For i = 0 To UBound(myArray)
If myArray(i) = val Then
found = True
End If
Next i
End If
IsInArray = found
End Function
This is what I would do.
For each thing in Arr
If instr(Thing, StringToBeFound) > 0 then msgbox thing
Next
Your second function uses a lot of memory with a big array.
This is what happens when you join strings. I don't know if Join function uses stringbuilding or not. I doubt it does as nothing else in basic does. So ordinary concatination shuffles a lot of bytes around memory.
String Concatination
And don't join strings one character at a time. See this from a VBScript programmer. It requires 50,000 bytes and many allocation and deallocation to make a 100 character string.
http://blogs.msdn.com/b/ericlippert/archive/2003/10/20/53248.aspx
PS: We have the Mid statement in VBA (not to be confused with the Mid function) that allows string building as a generic solution to shuffling bytes. It only matters on large arrays/strings.
Both solutions should work. The first one is small and straightforward, and moreover it returns how many times the match was found.
The second solution has the advantage of being "under your control", since you know what exactly you are doing. For example, with a slight modification you can return the position of the first match. You can make it faster though by adding the statement "Exit For" after "found = true", since there is no need to check further...
Thanks to several people who input in this thread I was able to create this solution
Function IsInArrayyyy(stringToBeFound As String, arr As Variant) As Boolean
For Each thing In arr
If InStr(thing, stringToBeFound) > 0 Then
If Len(thing) = Len(stringToBeFound) Then 'remove this if exact match not needed
IsInArrayyyy = True: Exit Function
End If 'remove this if exact match not needed
End If
Next
End Function

Check if property is an array

I want to check if a property from a class is an array (only concerned about numerical arrays here, NOT character arrays [i.e. strings]). I then want to iterate through the array (i.e. 'do something' with each element). See my attempt below. Thanks!!
edit:
So, a little more info... neither IsArray nor my method shown has worked thus far to check for an array. MSDN suggestions "typeof(Array).IsAssignableFrom(type)", but I wasn't sure how to make that work with the property info here. But maybe someone else knows how to use them and I just didn't use properly.
Within the "Class3" I define an array but to not dimension it. I use "redim" when I instantiate it in another thread and load it up prior to passing it to this function. When I insert a breakpoint in the code here, I can look at "myobject" and see the array elements and values, but really I'm looking to cleanly use the propertyinfo type to generalize this method. I also need to be able to index into the array once I've determined that it is an array...again using propertyinfo, not "myobject" directly.
Public Class Class2
Private Shared filelock As New Object
Public Shared Sub write2file(ByVal myobject As Class3)
SyncLock filelock
Dim sb As New StringBuilder
Using sw As StreamWriter = New StreamWriter(File.Open(newfilename, FileMode.Append, FileAccess.Write, FileShare.None))
'Dim pinfo() As PropertyInfo = GetType(Class3).GetProperties
Dim pinfo() As PropertyInfo = CType(myobject.GetType.GetRuntimeProperties, PropertyInfo())
sb.Clear()
For Each p As PropertyInfo In pinfo
If Not p.GetIndexParameters.Length > 0 Then 'if property is not an array
sb.Append(p.GetValue(myobject)).Append(",")
Else ' if property is an array
For x As Integer = 0 To p.GetIndexParameters.Length - 1
sb.Append(p.GetValue(myobject, New Object() {x})).Append(",") 'append each value from array to the stringbuilder in .CSV format
Next
End If
Next
sw.WriteLine(sb) 'write string to file
End Using
End SyncLock
End Sub
End Class
Well, this is not the prettiest thing to do (I somehow don't feel comfortable when comparing the type with a string... if anyone knows better please let me know), but I have tested it and it works:
For Each p As PropertyInfo In pinfo
Dim typeString As String = p.PropertyType.Name.ToString
If typeString = "Int32[]" Then 'if property is not an array
sb.Append(p.GetValue(myobject)).Append(",")
Else ' if property is an array
For x As Integer = 0 To p.GetIndexParameters.Length - 1
sb.Append(p.GetValue(myobject, New Object() {x})).Append(",") 'append each value from array to the stringbuilder in .CSV format
Next
End If
Next
Is it what you were looking for?
only concerned about numerical arrays here, NOT character arrays [i.e. strings]
If by "numerical" you mean that it could be any type (not just Integer), and you are 100% sure that the array it is either numerical or string (I mean, no boolean arrays for instance), then you can modify it to this:
If typeString.EndsWith("[]") And typeString <> "String[]" Then
I hope it helps...
Replace the If block with something like this and it seems to work with a trivial Class3 implementation with an Int32() array property:
If p.PropertyType.IsArray Then
' Untested loop code for array case
Else
' Untested code for scalar (non-array) case
End If
No one seems to have recommended the PropertyType property yet, but that's pretty crucial.

Resources