I know that in Visual Basic, delegate function cannot contain optional parameters. But can a method take a delegate as an optional parameter?
What I want to do is this:
Delegate Sub MyDelegate(ByVal input As String)
Sub MyDelegateDefault(ByVal input As String)
'by default do nothing'
End Sub
Sub MyDelegateCustom1(ByVal input As String)
'do something here'
End Sub
In a different part of code:
Sub OtherFunction(ByVal str As String, Optional ByVal delegate As MyDelegate = AddressOf MyDelegateDefault)
delegate(str)
End Sub
Sub ParentFunction()
OtherFunction("", ) '< "" as string, nothing for optional delegate parameter'
End Sub
Note how the final function OtherFunction takes a optional delegate as second parameter.
Is this a thing? Can a delegate function be an optional parameter?
A parameter that is of a reference type can only be defaulted to null. Change the default value to null, check for the null condition, and don't call the delegate (do nothing).
Related
I'm trying to pass an optional callback parameter to my class when I instantiate it. I need a callback method to format some value that may or may not need formatting.
Here is my simplified code of what I am trying to do:
Public Class ColumnSpec
Property Title As String
Property Name As String
Property Value As String
Delegate Function CallBack(aValue As String) As String
Property HasCallBack As Boolean
Public Sub New(aTitle As String, aName As String, Optional aCallBack As CallBack = Nothing)
_Title = aTitle
_Name = aName
' How do I assign aCallBack to the delegate function here?
_HasCallBack = aCallBack IsNot Nothing
End Sub
End Class
I may or may not pass a callback function when instantiating ColumnSpec.
In another class, I need to check and call the delegate.
Public Class MyClass
Public Function FormatText(Value as String) As String
Return Value + "01234" ' Return value after modifying
End Function
Public Function RenderValue() As String
Dim lNewCol1 as ColumnSpec("Title1", "Name2", AddressOf FormatText)
Dim lNewCol2 as ColumnSpec("Title2", "Name2")
' How do I check if there is a delegate and call it?
If lNewCol1.HasCallBack Then lNewCol1.Value = lNewCol1.CallBack(lNewCol1.HasCallBack)
If lNewCol2.HasCallBack Then lNewCol2.Value = lNewCol1.CallBack(lNewCol2.HasCallBack)
End Function
End Class
I am completely new to delegates. Any ideas on how I can solve this?
Your line Delegate Function CallBack(aValue As String) As String only declares the type of the callback. You need a separate property or field to hold the reference.
So, you would modify the class slightly:
Delegate Function CallbackFunction(aValue As String) As String
Public ReadOnly Property Callback As CallbackFunction
Public ReadOnly Property HasCallback As Boolean
Get
'Alternatively, you could assign the value in the ctor as in your code...
Return Callback IsNot Nothing
End Get
End Property
Public Sub New(Optional aCallback As CallbackFunction = Nothing)
Me.Callback = aCallback
End Sub
Your client code is fine as-is and doesn't need to be changed.
If you don't care about having a named type for the callback function, you could also use newer syntax, e.g.
Public ReadOnly Property Callback As Func(Of String, String)
Also modifying the constructor accordingly.
The client code would still work fine as-is with this modification.
(Action is to Sub as Func is to Function.)
This question already has answers here:
Using an enum as an optional parameter
(3 answers)
Closed 3 years ago.
When working with optional arguments, I like to default them to Nothing.
Sub DoSomething(ByVal Foo as String, Optional ByVal Bar as String = Nothing)
If Bar IsNot Nothing then DoSomethingElse(Bar)
DoAnotherThing(Foo)
End Sub
This works great, unless you start working with Enum types (or an Integer and other Data types).
In which case my Enum list includes a 'None' value, as follows:
Enum MyEnum
None
ChoiceA
ChoiceB
End Enum
Sub DoSomething(ByVal Foo as String, Optional ByVal Bar as MyEnum= MyEnum.None)
If Bar = MyEnum.None then DoSomethingElse(Bar)
DoAnotherThing(Foo)
End Sub
It works, but I am looking for alternatives. In addition to the burden of creating a 'None' entry in a custom Enum, this is just not possible to do with enumerations defined by the framework or a third party DLL.
In your example, it could make more sense to overload.
Sub DoSomething(ByVal Foo as String, ByVal Bar as MyEnum)
DoSomethingWithBar(Bar)
DoSomething(Foo)
End Sub
Sub DoSomething(ByVal Foo as String)
' Do something with Foo
End Sub
As is often the case, I went across a few answers as I was drafting the question.
This post and the .NetDocumentation suggest the use of a nullable:
Sub DoSomething(ByVal Foo as String, Optional ByVal Bar as Nullable(Of MyEnum) = Nothing)
If Bar IsNot Nothing then DoSomethingElse(Bar)
DoAnotherThing(Foo)
End Sub
Or,
Sub DoSomething(ByVal Foo as String, Optional ByVal Bar as Nullable(Of MyEnum) = Nothing)
If Bar IsNot Nothing then DoSomethingElse(Bar)
DoAnotherThing(Foo)
End Sub
Never used this so any comments / warnings going this way are most welcome!
I have the following code to write some text from different functions and subs but that has been working ok but now I'm getting the target parameter count exception when I call the delegate from SerialPort DataReceived event.
I can't figure out what I'm doing wrong, Any ideas?
Delegate Sub PrintSmsLogDelegate(ByVal NewText As String, ByVal NewLine As Boolean)
Protected Friend Sub PrintSmsLog(ByVal NewText As String, Optional ByVal NewLine As Boolean = True)
If Me.InvokeRequired Then
Dim Txt As New PrintSmsLogDelegate(AddressOf PrintSmsLog)
'Me.Invoke(Txt, NewText)'This fail too
Me.Invoke(Txt, New Object() {NewText}) '<--- TargetParameterCountException
Else
'...
End If
End Sub
Private Sub SmsSerialPort_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SmsSerialPort.DataReceived
'... code to receive data and save it in "Lines" variable
Dim Lines as String
Me.PrintSmsLog(Lines, False)
End Sub
The problem is that your PrintSmsLogDelegate delegate declaration contains 2 required parameter.
So you have to provide the 2nd parameter as well.
The method signature for Invoke method is this:
Function Control.Invoke(method As [Delegate], ParamArray args As Object()) As Object
So you should call your PrintSmsLogDelegate delegate instance (which is Txt) with two parameters even if the PrintSmsLog method does not require the 2nd parameter.
Me.Invoke(Txt, NewText, True)
You cannot call the Invoke method with a single array parameter. Due to ParamArray keyword an array will be automatically created of the multiple parameter you specify.
Hello. I have a small problem and I can't figure it out: how can I create an optional parameter for a form's name? For example I want to do something like this:
Private Sub Draw(ByVal Start_Pos As Point, ByVal End_Pos As Point, Optional ByVal Form_Name As Form = Cube)
End Sub
I'm not sure if what I want is possible.I just know that the code is not correct because I must specify to the program that "Cube" is a form not just a string...
A constant value is required for Optional params.
When a constant value is not possible to establish at design time for whatever reason, then the easiest trick is to set the vaue as Nothing and check if the value is nothing inside the block, if it is, then set their default value at execution time.
An example:
Private Sub Draw(ByVal startPos As Point, ByVal endPos As Point,
Optional ByVal form As Form = Nothing)
If (form Is Nothing) Then
form = Cube
End If
' ...
End Sub
An adaptation to the real problem that you'd described:
Private Sub Draw(ByVal startPos As Point, ByVal endPos As Point,
Optional ByVal formName As String = "")
If ( String.IsNullOrEmpty(formName) ) Then
formName = Cube.Name
End If
' ...
End Sub
As mentioned in documents for Optional Parameters for each optional parameter, you must specify a constant expression as the default value of that parameter.
So you can't use a form instance as default value for optional parameter.
If you need to set a form name as default value for your string optional parameter:
You can set one of your application forms full name (including namespace) as default value and then create an instance of that form using Activator.CreateInstance and Unwrap the object and use DirectCast to cast it to form and show it later.
To create other forms that their names passed as that parameter, you can use Activator.CreateInstance also you can have a Dictionary(Of String, Type) containing names and form types or Dictionary(Of String, Form) containing names and form instances and use this dictionary to get the instance of form.
I'm doing this:
Delegate Sub SetTextBoxText_Delegate(ByVal [Label] As TextBox, ByVal [text] As String)
' The delegates subroutine.
Public Sub SetTextBoxText_ThreadSafe(ByVal [Label] As TextBox, ByVal [text] As String)
' InvokeRequired required compares the thread ID of the calling thread to the thread ID of the creating thread.
' If these threads are different, it returns true.
If [Label].InvokeRequired Then
MsgBox("invoke")
Dim MyDelegate As New SetTextBoxText_Delegate(AddressOf SetTextBoxText_ThreadSafe)
Me.Invoke(MyDelegate, New Object() {[Label], [text]})
Else
MsgBox("noinvoke")
[Label].Text = [text]
End If
End Sub
However it always uses noinvoke. If I try setting it normaly it gives me a thread-safe warning and doesn't work. If I force invoke then it says the control isn't created?
Could someone help?
It's most likely because the control has not yet been created when you try to access it. Wait until the control has loaded, or check it using Label.Created. Like so:
Public Sub SetTextBoxText_ThreadSafe(ByVal Label As TextBox, ByVal text As String)
If Label.Created Then
If Label.InvokeRequired Then
MsgBox("invoke")
Dim MyDelegate As New SetTextBoxText_Delegate(AddressOf SetTextBoxText_ThreadSafe)
Me.Invoke(MyDelegate, New Object() {Label, text})
Else
MsgBox("noinvoke")
Label.Text = text
End If
End If
End Sub
P.S. You don't need a custom delegate type, just use Action(Of TextBox, String). You also don't need square brackets around Label or text.