How would I prevent other developers from enter "" or " " into the following function/sub?
Public Sub MyFunction(MyString as String)
End Sub
' Call:
MyFunction("")
I want them to end up with a non compileable app.
There is no way to prevent compilation based on what is passed to the string. You can, however, simply prevent the method from executing, like this:
Public Sub MyFunction(myString as String)
If Not String.IsNullOrWhitespace(myString) Then
' Do stuff here
End If
End Sub
Your other option is to throw an exception:
Public Sub MyFunction(myString as String)
If String.IsNullOrWhitespace(myString) Then
Throw New ApplicationException("No empty or whitespace strings allowed!")
Else
' Do stuff here
End If
End Sub
Related
I have discovered that referencing a member variable in a lambda expression executed in a Task throws a NullReferenceException when accessing it using the With statement.
For example I expect the following code to print two lines on the console. The first one accesses the SomeString member via obj.SomeString while the second one uses the With statement and accesses the member via .SomeString. I expected both options to be equivalent but the second one throws an exception.
Class Foo
Public SomeString As String
End Class
Module Module1
Sub Main()
Dim obj As New Foo With {.SomeString = "Hello World"}
With obj
Task.Factory.StartNew(
Sub()
Console.WriteLine("1:" + obj.SomeString) ' works
Console.WriteLine("2:" + .SomeString) ' NullReferenceException here
End Sub)
End With
Console.ReadKey()
End Sub
End Module
When I move the Console.ReadKey() statement into the With statement, the code works.
I fixed the actual code by not using the With statement but I still don't know what concept I'm missing here. Why can I access members of the obj variable in the lambda expression but not the members of the With expression? It has not been garbage collected because I can still see it in the debugger when the exception is thrown. The expression seems to go out of scope (or something like that) but why doesn't the compiler just do what I expect and treats it the same as obj?
It is because of the voodoo that the VB compiler does to support the With block and lambda expressions. If you look at your code through a decompiler like Redgate's Reflector, your code gets converted into something like the code below except that I renamed the variables to ones supported by VB; they can be quite long and include characters that are invalid for VB variable names
<STAThread> _
Public Shared Sub Main()
Dim var1 As New GeneratedClass1
Dim foo As New Foo With {.SomeString = "Hello World"}
var1.objVar = foo
Dim var2 As New GeneratedClass1.GeneratedClass2 With {.var2 = var1, .theWithVariable = var1.objVar}
Task.Factory.StartNew(New Action(AddressOf var2._Lambda___1))
var2.theWithVariable = Nothing
Console.ReadKey()
End Sub
<CompilerGenerated> _
Friend Class GeneratedClass1
' Methods
<DebuggerNonUserCode> _
Public Sub New()
End Sub
<DebuggerNonUserCode> _
Public Sub New(ByVal other As GeneratedClass1)
If (Not other Is Nothing) Then
Me.objVar = other.objVar
End If
End Sub
' Fields
Public objVar As Foo
' Nested Types
<CompilerGenerated> _
Friend Class GeneratedClass2
' Methods
<DebuggerNonUserCode> _
Public Sub New()
End Sub
<DebuggerNonUserCode> _
Public Sub New(ByVal other As GeneratedClass2)
If (Not other Is Nothing) Then
Me.theWithVariable = other.theWithVariable
End If
End Sub
<CompilerGenerated> _
Public Sub _Lambda___1()
Console.WriteLine(("1:" & Me.var2.objVar.SomeString))
Console.WriteLine(("2:" & Me.theWithVariable.SomeString))
End Sub
' Fields
Public theWithVariable As Foo
Public var2 As GeneratedClass1
End Class
End Class
You can see that the compiler creates a class that holds a reference to the With variable and the method of the lambda expression. As soon as the With variable is out of scope it is set to Nothing and hence the null reference expression when task executes.
I am about to refactor some old VisualBasic application and have come along with the following situation:
Public Sub MySub ()
Try
' execute dangerous operation
Catch ex As System.Exception
Call HandleErrors((ErrObject) ex) ' <-- invalide cast
End Try
End SuB
Public Sub HandleErrors(ByRef objError As ErrObject) ' I can not easily change the signature of this sub as it gets referenced very often.
' process error ..
End Sub
I would like to reuse the already existing Sub ‘HandleErrors()’, which takes an ErrObject as parameter. But since I am more comfortable to use Try and Catch, I would like to pass an Object of the type Syste.Exception, rather than an ErrObject.
Question:
Is there any way of casting or transforming the Exception into an ErrObject ?
Rather than trying to force exceptions into the historic Visual Basic error handling routines, I think the better option here is to create a new overload of HandleErrors (leaving the original sub signature untouched for the places that still use it), possibly with an entirely new function that both the original function and new overload can then call. For instance, lets's assume your function currently looks like this:
Public Sub HandleErrors(ByRef objError As ErrObject)
Log(objError.Description)
PerformSomeGlobalCleanup()
End Sub
And now you want to also be able to log the new-style exceptions too. You could just create a new overload:
Public Sub HandleErrors(ByRef ex As Exception)
Log(ex.Message)
PerformSomeGlobalCleanup()
End Sub
But it would be better for this code to share the underlying structure and logic of whatever HandleErrors is actually doing, so you could do this for instance:
Private Sub InternalHandleErrors(ByVal msg as String)
Log(msg)
PerformSomeGlobalCleanup()
End Sub
Public Sub HandleErrors(ByRef ex As Exception)
InternalHandleErrors(ex.Message)
End Sub
Public Sub HandleErrors(ByRef objError As ErrObject)
'original signature, but refactored
InternalHandleErrors(objError.Description)
End Sub
You can move as much logic from the original HandleErrors into InternalHandleErrors as makes sense - whatever is common between handling an ErrObject and an Exception.
This means you're not "polluting" the newly refactored code with old-style Visual Basic objects, and means that if/when you complete the refactoring to remove the original function when nothing else references it, you don't need to go through your entire code base removing the casts.
Ok,since it seems there is no easy way to do this, I decided to overload the HandleError Sub and introduce a new class to generalise the Exceptions.
Public Sub MySub ()
Try
' execute dangerous operation
Catch ex As System.Exception
Call HandleErrors((ErrObject) ex) ' <-- invalide cast
End Try
End SuB
Class GeneralError
Public ReadOnly Number As Integer
Public ReadOnly Source As String
Public ReadOnly Description As String
Sub New(exception As System.Exception)
Number = 1
Source = exception.Source
Description = exception.Message
End Sub
Sub New(errObject As ErrObject)
Number = errObject.Number
Source = errObject.Source
Description = errObject.Description
End Sub
End Class
Public Sub HandleErrors(ByRef errObject As ErrObject) ' Overload
Dim generalError As GeneralError = New GeneralError(errObject )
Call HandleErrors(generalError)
End Sub
Public Sub HandleErrors(ByRef exception As Exception) ' Overload
Dim generalError As GeneralError = New GeneralError(exception)
Call HandleErrors(generalError)
End Sub
Private Sub HandleErrors(ByRef generalError As GeneralError) ' Original
' process error ..
End Sub
I wrote a module and into which a Public Sub Main method. But, when I run the program. It gives " No accessible 'Main' method with an appropriate signature was found in 'abc'."
Could you please suggest possible solutions to the error.
Public Sub Main(ByVal cmdArgs As String)
Dim returnValue As Integer
If cmdArgs.Length > 0 Then
returnValue = Import_Start(cmdArgs, "f9880")
Console.WriteLine("Import end with an error " & returnValue)
Else
Console.WriteLine("parameter failure")
End If
End Sub
End Module
If you want to start your app from a Sub Main, the correct signature is:
Public Sub Main(args As String())
' or
Public Sub Main()
The command line args will be passed as a string array. Yours just declares it as String resulting in the compiler error. You also need to set the StartUp object to Sub Main in Project Properties, but that already seems to have been done.
If you do not want/need to use a module you can add it to a form (it is not clear if this is even a WinForms app) using:
Public Shared Sub Main(args As String())
' or
Public Shared Sub Main()
Frm1 contains the code for validation of textbox:
Public Function AlphabeticalOnly(ByVal Str As String) As Boolean
Dim pattern As String = "^[a-zA-Z\s]+$"
Dim reg As New Regex(pattern)
If reg.IsMatch(Str) = False Then
MsgBox(Str & " is invalid! Please enter alphabetical characters only!", MsgBoxStyle.Critical, "Error")
End If
Return reg.IsMatch(Str)
End Function
Because there're quite an amount of validations, I don't want to repeat all the code again in the other forms.
Private Sub btnDone_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDone.Click
If AlphabeticalOnly(txtName.Text) = False Then
Exit Sub
End If
...
End Sub
I tried the code above in another form, but the error list shows that AlphabeticalOnly is not declared.
Is there anything that I need to add to my code?
First of all, don't put the function on a form. If it's common code shared by all forms, put it in its own class file.
Second, this common code shouldn't be prompting the user with a message box. This function should just perform the logic and nothing more. (This also makes the function easier to unit test.) Then allow the consuming code (in this case a form) to interact with the user. (Especially since the current implementation checks the match twice, which isn't necessary.)
Since this function doesn't rely on object state, you can make it Shared. Something like this:
Public Class CommonFunctions
Public Shared Function IsAlphabeticalOnly(ByVal Str As String) As Boolean
Dim pattern As String = "^[a-zA-Z\s]+$"
Dim reg As New Regex(pattern)
Return reg.IsMatch(Str)
End Function
End Class
Then on your forms you can invoke that function:
If CommonFunctions.IsAlphabeticalOnly(txtName.Text) = False Then
MsgBox(Str & " is invalid! Please enter alphabetical characters only!", MsgBoxStyle.Critical, "Error")
End If
Okay I want to add something to this macro
Sub Search()
Inputbox myInput
found = false
loop
Call getInput (myInput) '~> check multiple files
end loop
If found = false
'DO something
End if
End sub
Sub getInput(ByVal inputVar As String, ByVal Input as Boolean)
If a = inputVar Then
found = true '~> I want to pass this parameter back to search
End If
End sub
The case is like, I want my sub to pass the found parameter from
Search() to getInput() and then getInput() return the found parameter to
Search()
Should I add something like search(ByVal found as boolean) ?
if you want to return a value, then you should change the getInput Sub to a function as they can return values.
Sub Search()
Dim found As Boolean
InputBox myInput
found = checkInput(myInput)
If found = False Then
'DO something
End If
End Sub
Function checkInput(inputVar As String) As Boolean
Dim result As Boolean
'do your checking here and set result
'return the result
checkInput = result
End Function