When is it suitable to use a Sub-Procedure instead of a function? - vb.net

In my class today I was told change some of my sub-procedures to functions, and when I asked why it's better my teacher struggled to answer, generally, i've always thought that functions should only really be used when a value is returned. In the two examples below; is there one method that should be used over the other, or does it not matter? And if it does matter why?
Thanks in advance.
Method 1 (Sub-Proc):
Sub EncryptString(ByVal unkString, ByRef encryptedString)
For i = 1 To Len(unkString)
encryptedString += "*"
Next
End Sub
Method 2 (Function):
[In main I assign the variable "encryptedString" to this function].
Function encryptString(ByVal unkString) As String
For i = 1 To Len(unkString)
encryptString += "*"
Next
End Function

You've misunderstood what they're trying to tell you. In your Function example there is no difference. What your teacher is expecting is like this:
Function EncryptString(ByVal unkString) As String
Dim encryptedString As String = ""
For i = 1 To Len(unkString)
encryptedString += "*"
Next
Return encryptedString
End Function
This is a cleaner and more reusable way than modifying a field, an argument passed ByRef, or the underlying variable of the function

Your example show one of the multiple reason, who initialize the data is unclear. With your sample code, the first option would append to the passed string while the second would create a new string.
The first method would have to specify if it needs an empty string or explain why it appends. While the second method clearly show that a new string will be returned.
Sub Main()
Dim u, e As String
u = "123"
e = "123"
EncryptString1(u, e)
Console.WriteLine(e) ' Display: 123***
u = "123"
e = "123"
e = encryptString2(u)
Console.WriteLine(e) ' Display: ***
Console.ReadLine()
End Sub
Sub EncryptString1(ByVal unkString As String, ByRef encryptedString As String)
For i As Integer = 1 To Len(unkString)
encryptedString += "*"
Next
End Sub
Function encryptString2(ByVal unkString As String) As String
encryptString2 = ""
For i As Integer = 1 To Len(unkString)
encryptString2 += "*"
Next
End Function
Please have option strict on. Also, personally, I rather create a variable instead of using the function name, use .Length instead of Len() and concatenate with & instead of +.
Function encryptString3(ByVal unkString As String) As String
Dim encryptedString As String = ""
For i As Integer = 1 To unkString.Length
encryptedString &= "*"
Next
Return encryptedString
End Function
Or just use the New operator of the String class.
Dim encryptedString as New String("*"c, unkString.Length)

Well, when I was learning this stuff, it was always to use functions to calculate values and subs to do other stuff. I guess for something very general, it doesn't really matter which methodology you use, as you have illustrated in your example. See the link below for further discussion on this topic.
http://analystcave.com/vba-function-vs-vba-sub-procedures/

Related

VBA Function Passing Multi Variables back to Sub

I have a large string over 500 char which is called strEssay. I want to use a function(since I will need to look for several patterns) to return two values if (for example the name) Frank is found or not.
This is the function I'm trying to use:
Function NameFinder(strEssay as String, strName as String)
Dim varNameCounter as Variant
Dim strNameFinderResult as String
varNameCounter = 0
strNameFinderResult = ""
If strEssay like "*" & strName & "*" Then
strNameFinderResult = strName
varNameFinderCounter = 1
Else
strNameFinderResult = ""
varNameFinderCounter = .001
EndIf
End Function
I want to be able to return back to my subroutine both 'strNameFinderResult' and 'varNameFinderCounter'.
Is there any way that I can return both values?
If I can't return both simultaneously can I return one through the function and the other through a textbox or something? What would calling the function look like in the subroutine and/or how would I need to change my function?
NameFinder() function, returning array of 3 elements. It is called and returned by TestMe(), writing the following to the console:
Function NameFinder(essay As String, name As String)
Dim nameFinderResult As String
Dim namefinderCounter As String
nameFinderResult = "" & essay & name
namefinderCounter = 0.001 + 12
NameFinder = Array(nameFinderResult, namefinderCounter, "something else")
End Function
Public Sub TestMe()
Dim myArray As Variant
myArray = NameFinder("foo", "bar")
Dim i As Long
For i = LBound(myArray) To UBound(myArray)
Debug.Print myArray(i)
Next i
End Sub
As a general rule, you have to give the routine a type like this:
Function NameFinder(strEssay as String, strName as String) as string
But, that returns only ONE value.
So, a function (as opposed to a sub) returns one value (as a general rule).
However, you CAN also return parameters that you pass. I mean, in above, you can't make TWO assignments to one variable, can you?
So, you can use a Sub like this:
Sub NameFinder(strEssay as String, strName as String, _
strNameFinderResult as string, _
varNameFinderCounter as double)
If strEssay like "*" & strName & "*" Then
strNameFinderResult = strName
varNameFinderCounter = 1
Else
strNameFinderResult = ""
varNameFinderCounter = .001
EndIf
So in code, you now can go:
dim strMyResult as string
dim strFinderCount as Double
Call NameFinder("MyEassy", "Joe Blow", strMyResult, strFinderCount)
So, you can return values with the parameters.
Now, I suppose it possible for some strange reason, that you want to use a function to return two values with a single assignment?
What you would do is this in your code module.
Define a custom type, and use that.
eg this:
Option Compare Database
Option Explicit
Type SearchResult
strName As String
FindCount As Double
End Type
Function NameFinder(strEssay As String, strName As String) As SearchResult
NameFinder.FindCount = 0
NameFinder.strName = ""
If strEssay Like "*" & strName & "*" Then
NameFinder.strName = strName
NameFinder.FindCount = 1
Else
NameFinder.strName = ""
NameFinder.FindCount = 0.001
End If
End Function
So, now to use in code? You can go like this:
dim MyResults as SearchResult
MyResults = NameFinder("My eassy", "Joe Blow")
debug.print "Name found result = " & MyResults.strName
debug.print "Count of find = " & MyResult.FindCount
The VERY nice thing about above is you get full intel-sense in your code editor.
eg this:
So by building a custom data type, you can use "one" assignment for the return type. And you get nice type checking and inteli-sense in the VBA code editor.
And you can even do this:
But, to get both variables, then you would in theory wind up calling the function two times. So, you can actually use the function without declarer of variables like this:
Debug.Print NameFinder("MyEassy", "Joe blow").strName
Debug.Print NameFinder("MyEassy", "Joe blow").FindCount
So, I don't recommend the above, but in the case in which you ONLY want one of the return values, then the raw expression (function) like above would be a use case (and no need to even declare a return variable).
But, without a doubt, define a custom type in code as per above. The reason is now you get a really nice VBA editor type-checking, inteli-sense, and also that you only have to declare "one" variable that holds two values.
In fact, the results are very much like JavaScript, or even c# in which you declare a "class" type. So with a custom "type" you are declaring a data type of your own. And the beauty of this is if you need say 3 values, then once again you create a type with 3 "inside" values.
The you ONLY have to declare that one variable as the custom type.
With this you get:
Very valuable compile time syntax and data type checking of the var types you are using.
You get GREAT VBA inteli-sense while coding - which means less coding mistakes.
And you type far less typing in the VBA editor as it will pop-up the choices for you as you write code. And you can't type or choose the wrong sub - type, as the compiler will catch this.

CountWord and CountVowel into Function in VB.NET

Hi I need to change WordCount and CountVowel procedures to functions and create a function to count number of consonants in a string.
I have done these two procedures but I cannot figure out how to do the last part. I am fairly new to programming.
My current code is given below:
Sub Main()
Dim Sentence As String
Console.WriteLine("Sentence Analysis" + vbNewLine + "")
Console.WriteLine("Enter a sentence, then press 'Enter'" + vbNewLine + "")
Sentence = Console.ReadLine()
Console.WriteLine("")
Call WordCount(Sentence)
Call VowelCount(Sentence)
Console.ReadLine()
End Sub
Sub WordCount(ByVal UserInput As String)
Dim Space As String() = UserInput.Split(" ")
Console.WriteLine("There are {0} words", Space.Length)
End Sub
Sub VowelCount(ByVal UserInput As String)
Dim i As Integer
Dim VowelNumber As Integer
Dim Vowels As String = "aeiou"
For i = 1 To Len(UserInput)
If InStr(Vowels, Mid(UserInput, i, 1)) Then
VowelNumber = VowelNumber + 1
End If
Next
Console.WriteLine("There are {0} vowels", VowelNumber)
End Sub
Thanks for your time
I would use the following three functions. Note that WordCount uses RemoveEmptyEntries avoids counting empty words when there are multiple spaces between words.
The other two functions count upper case vowels as vowels, rather than just lower case. They take advantage of the fact that strings can be treated as arrays of Char, and use the Count method to count how many of those Chars meet certain criteria.
Note that the designation of "AEIOU" as vowels may not be correct in all languages, and even in English "Y" is sometimes considered a vowel. You might also need to consider the possibility of accented letters such as "É".
Function WordCount(UserInput As String) As Integer
Return UserInput.Split({" "c}, StringSplitOptions.RemoveEmptyEntries).Length
End Function
Function VowelCount(UserInput As String) As Integer
Return UserInput.Count(Function(c) "aeiouAEIOU".Contains(c))
End Function
Function ConsonantCount(UserInput As String) As Integer
Return UserInput.Count(Function(c) Char.IsLetter(c) And Not "aeiouAEIOU".Contains(c))
End Function
To turn each of your Sub routines into a Function, you need to do three things. First, you need to change the Sub and End Sub keywords to Function and End Function, respectively. So:
Sub MyMethod(input As String)
' ...
End Sub
Becomes:
Function MyMethod(input As String)
' ...
End Function
Next, since it's a function, it needs to return a value, so your Function declaration needs to specify the type of the return value. So, the above example would become:
Function MyMethod(input As String) As Integer
' ...
End Function
Finally, the code in the function must actually specify what the return value will be. In VB.NET, that is accomplished by using the Return keyword, like this:
Function MyMethod(input As String) As Integer
Dim result As Integer
' ...
Return result
End Function
So, to apply that to your example:
Sub WordCount(ByVal UserInput As String)
Dim Space As String() = UserInput.Split(" ")
Console.WriteLine("There are {0} words", Space.Length)
End Sub
Would become:
Function WordCount(userInput As String) As Integer
Dim Space As String() = UserInput.Split(" ")
Return Space.Length
End Sub
Note, ByVal is the default, so you don't need to specify it, and parameter variables, by standard convention in .NET are supposed to be camelCase rather than PascalCase. Then, when you call the method, you can use the return value of the function like this:
Dim count As Integer = WordCount(Sentence)
Console.WriteLine("There are {0} words", count)
As far as counting consonants goes, that will be very similar to your VowelCount method, except that you would give it the list of consonants to look for instead of vowels.
You could use the Regex class. It's designed to search for substrings using patterns, and it's rather fast at it too.
Sub VowelCount(ByVal UserInput As String)
Console.WriteLine("There are {0} vowels", System.Text.RegularExpressions.Regex.Matches(UserInput, "[aeiou]", System.Text.RegularExpressions.RegexOptions.IgnoreCase).Count.ToString())
End Sub
[aeiou] is the pattern used when performing the search. It matches any of the characters you've written inside the brackets.
Example:
http://ideone.com/LEYC30
Read more about Regex:
MSDN - .NET Framework Regular Expressions
MSDN - Regular Expression Language - Quick Reference
VB is no longer a language I use frequently but I don't think I'm going to steer you wrong even without testing this out.
Sub Main()
Dim Sentence As String
Console.WriteLine("Sentence Analysis" + vbNewLine + "")
Console.WriteLine("Enter a sentence, then press 'Enter'" + vbNewLine + "")
Sentence = Console.ReadLine()
Console.WriteLine("")
'usually it's better just let the function calculate a value and do output elsewhere
'so I've commented your original calls so you can see where they used to be
'Call WordCount(Sentence)
Console.WriteLine("There are {0} words", WordCount(Sentence))
'Call VowelCount(Sentence)
Console.WriteLine("There are {0} vowels", VowelCount(Sentence))
Console.ReadLine()
End Sub
Function WordCount(ByVal UserInput As String) As Integer
Dim Space As String() = UserInput.Split(" ")
WordCount = Space.Length
'or just shorten it to one line...
'Return UserInput.Split(" ").Length
End Function
Function VowelCount(ByVal UserInput As String) As Integer
Dim i As Integer
Dim VowelNumber As Integer
Dim Vowels As String = "aeiou"
For i = 1 To Len(UserInput)
If InStr(Vowels, Mid(UserInput, i, 1)) Then
VowelNumber = VowelNumber + 1
End If
Next
VowelCount = VowelNumber
End Function
The most obvious change between a sub and a function is changing the keywords that wrap up the procedure. For this conversation let's just say that's one good word to use for encompassing both concepts since they're very similar and many languages don't really draw such a big distinction.
For Visual Basic's purposes a function needs to return something and that's indicated by the As Integer that I added to the end of both of the function declarations (can't remember if that's the right VB terminology.) Also in VB you return a value to the caller by assigning to the name of the function (also see edit below.) So I replaced those lines that were WriteLines with appropriate assignments. Last I moved those WriteLine statements up into Main. The arguments needed to be changed to use the function return values rather than the variables they originally referenced.
Hopefully I'm not doing your homework for you!
EDIT: Visual Basic underwent a lot of changes to the language during the move to .Net back in the early 2000's. I had forgotten (or possibly not even realized) that the new preferred choice for returning a value is now more in line with languages like C#. So rather than assigning values to WordCount and VowelCount you can just use Return. One difference between the two is that a Return will cause the sub/function to exit at that point even if there is other code afterward. This might be useful inside an if...end if for example. I'm hoping this helps you learn something rather than just being confusing.
EDIT #2: Now that I see the accepted answer and re-read the question it seems there was a small part about counting consonants that got overlooked. At this point I assume this was indeed a classroom exercise and the intended answer was possibly even to derive the consonant count by using the other functions.
Here you go.
Function WordCount(ByVal UserInput As String) As Integer
Dim Space As String() = UserInput.Split(" ")
Return Space.Length
End Function
Function VowelCount(ByVal UserInput As String) As Integer
Dim i As Integer
Dim VowelNumber As Integer
Dim Vowels As String = "aeiou"
For i = 1 To Len(UserInput)
If InStr(Vowels, Mid(UserInput, i, 1)) Then
VowelNumber = VowelNumber + 1
End If
Next
Return VowelNumber
End Function
Function ConsonantCount(ByVal UserInput As String) As Integer
Dim i As Integer
Dim ConsonantNumber As Integer
Dim Consonants As String = "bcdfghjklmnpqrstvwxyz"
For i = 1 To Len(UserInput)
If InStr(Consonants, Mid(UserInput, i, 1)) Then
ConsonantNumber = ConsonantNumber + 1
End If
Next
Return ConsonantNumber
End Function

Checking is StringBuilder() AppendLine() is an empty string

I want to conditionally append a string, derived from a function, to a string builder. The required condition is that the function is not returning a blank string ("").
The purpose of conditionally appending the string is to avoid AppendLine() appending a line when the string (returned from the function) being appended is empty.
My current code (wrapping the function in a condition calling the very same function):
Dim builder As New System.Text.StringBuilder()
builder.Append("Some text...").AppendLine()
If Not IsNothing(someFunction(someParameterAA, someParameterAB)) Then
builder.Append(someFunction(someParameterAA, someParameterAB)).AppendLine()
End If
If Not IsNothing(someFunction(someParameterBA, someParameterBB)) Then
builder.Append(someFunction(someParameterBA, someParameterBB)).AppendLine()
End If
builder.AppendLine().Append("...some text.")
Dim s As String = builder.ToString
MessageBox.Show(s)
It is my hope that a more efficient alternative exists (efficient in terms of the amount of code to be written). Ultimately, I'd like to avoid calling the same function twice however I cannot independently add the builder.Append line of code to my function. Is it instead possible to target builder.Append?
Example of the potential logic:
If `builder.Append()` inside the following brackets is not an empty string then:
(
builder.Append(someFunction(someParameterAA, someParameterAB)).AppendLine()
)
If anyone has a solution on the above, bear in mind the prequisite of concision (=< 2) lines of code additional to the builder.Append() line.
I'm open to any other suggestions.
Create another method to do the appending like so:
CheckBeforeAppend(someFunction(someParameterAA, someParameterAB), builder)
CheckBeforeAppend(someFunction(someParameterBA, someParameterBB), builder)
....
Public Sub CheckBeforeAppend(s As String, sb As StringBuilder)
If Not String.IsNullOrEmpty(s)
sb.Append(s).AppendLine()
End If
End Sub
A simple refactor such as this shortens your original code and means you don't need to duplicate the null or empty check on the return value of your function.
And for the extension method (place this code in a Module):
<Extension()>
Public Sub CheckBeforeAppend(s As String, sb As StringBuilder)
If Not String.IsNullOrEmpty(s)
sb.Append(s).AppendLine()
End If
End Sub
usage:
someFunction(someParameterAA, someParameterAB).CheckBeforeAppend(sb)
or for an extension on StringBuilder:
<Extension()>
Public Sub CheckBeforeAppend(sb As StringBuilder, s As String)
If Not String.IsNullOrEmpty(s)
sb.Append(s).AppendLine()
End If
End Sub
usage:
builder.CheckBeforeAppend(someFunction(someParameterAA, someParameterAB))
You can avoid calling the function twice by storing the result of the function in a variable.
Dim myString As String = someFunction(someParameterAA, someParameterAB)
If myString <> "" Then
builder.Append(myString).AppendLine()
End If
myString = someFunction(someParameterBA, someParameterBB)
If myString <> "" Then
builder.Append(myString).AppendLine()
End If
This way you just call the function once and check your conditions with the variable. Also the code looks a lot smaller and more understandable.

Function convert cyrillic to latin

I am trying to create custom convert cyrillic to latin text function in VB.net. I've never tried to make a custom function so I don't know what I am doing wrong. I have one problem and also, function doesn't work : Object reference not set to an instance of an object.
Public Function ConvertCtoL(ByVal ctol As String) As String
ctol = Replace(ctol, "Б", "B")
ctol = Replace(ctol, "б", "b")
**End Function** ' doesn't return a value on all code paths
Since I didn't found a solution for cyrillic to latin text I was planning to create function that would replace every letter from one alphabet to another.
You need Return ctol to tell it what value to return.
Perhaps researching "lookup table" would help you make a neater function.
Edit: The Wikipedia entry for Lookup table should be a good start.
Here is a simple example:
Imports System.Text
Module Module1
Function ReverseAlphabet(s As String) As String
Dim inputTable() As Char = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray()
Dim outputTable() As Char = "ZYXWVUTSRQPONMLKJIHGFEDBCA".ToCharArray()
Dim sb As New StringBuilder
For Each c As Char In s
Dim inputIndex = Array.IndexOf(inputTable, c)
If inputIndex >= 0 Then
' we found it - look up the value to convert it to.
Dim outputChar = outputTable(inputIndex)
sb.Append(outputChar)
Else
' we don't know what to do with it, so leave it as is.
sb.Append(c)
End If
Next
Return sb.ToString()
End Function
Sub Main()
Console.WriteLine(ReverseAlphabet("ABC4")) ' outputs "ZYX4"
Console.ReadLine()
End Sub
End Module

Function return string or boolean

I want a function to return a String or Boolean. Something like this:
Public Function GetString(Byval What As String) 'As... someting?
If (What = "A") Then
Return "String to return"
Else if (What = "B") Then
Return True
End If
Return False 'Nothing to return
End Function
How can i now do this? If i ask like
If GetString("A") Then
MsgBox(GetString())
End IF
...it returns a string and of course it gives an error on converting string to bool.
I could always return strings and checks it lengths, but it feels bad. Or maybe I'm just into PHP too much?
But is there a way to do it more like this? If i ask for "B" i know it would return a bool, if i ask for "A" i want to alert the string if there was any and so on.
How can i now do this?
You can't.
A function can only return one type, not multiple.
You can return a custom type that contains a string and a boolean.
I would use an Array list. You can store whatever type you need in the list and then parse it on the return. This is really not best practice as explained above, but when you gotta get things done... The end justifies the means. Not recommended.
Public Function GetString(Byval What As String) As ArrayList
Dim b as boolean = True
dim myArrayList as Arraylist = New ArrayList
If (What = "A") Then
ArrayList.Add("String to return")
Else if (What = "B") Then
ArrayList.Add(b)
End If
Return False 'Nothing to return
End Function
Proof of concept below:
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim a As Boolean = True
Dim myarraylist As ArrayList = New ArrayList
myarraylist.Add(a)
myarraylist.Add("g")
Debug.WriteLine(myarraylist.GetType.ToString)
Debug.WriteLine(myarraylist(0).GetType.ToString)
Debug.WriteLine(myarraylist(1).GetType.ToString)
If myarraylist(0).GetType.ToString = "System.string" Then
Debug.WriteLine("Function returned a String")
ElseIf myarraylist(0).GetType.ToString = "System.boolean" Then
Debug.WriteLine("Function returned a Boolean")
End If
End Sub
You can return Object, but it is considered very bad form for a function to return 2 data types.
As Oded said, you can't return more than one parameter from a function.
It is not too clear what you are doing from your code example, but you may look into passing parameters by reference. As pointed out in the answer there, passing a parameter by reference is useful for:
when you want to return a state or status of an operation plus the result from the operation.
This is how int.TryParse and similar methods work.