Pass and store optional delegate in variable - vb.net

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.)

Related

Detecting or preventing assignment operator to a class

Is there any way to make a class can be only initialized at declaration.
Public Class AnyValue
Private value As Int32
Public Sub New(ByVal aValue As Int32)
value = aValue
End Sub
End Class
'I want to be able to do this:
Dim val As New AnyValue(8)
'But not this.
val = New AnyValue(9)
Or it is possible to stop the assignment or detect when the operator = is used.
Lets just say this - No, you can't do what you want. The closest thing to it that I can think of, is to hide the constructor and give static access to the consumer as follows:
Public Class AnyValue
Private value As Int32
Private Sub New(ByVal aValue As Int32) ' Note - private constructor
value = aValue
End Sub
Public Shared Function Create(ByVal aValue As Int32) As AnyValue
Return New AnyValue(aValue)
End Function
End Class
'This will not work
Dim val As New AnyValue(8)
'This will not work
val = New AnyValue(9)
' This will work
Dim val As AnyValue = AnyValue.Create(8)
Now, if you look at this method of object creation, you can see that you can set all sort of rules for object construction. So, the client has very little input on the construction itself because how you construct the object is totally controlled by the object itself.

Can a class's properties be assigned using a lookup, or must each property be assigned individually?

VS2013, Visual Basic
I have a class with many properties.
Public Class
Property 1
.
.
Property N
End Class
I have a list of name value pairs
name1, value1
.
.
nameN, valueN
The values in the name value pairs will be assigned to the property values.
Does VB have a way that allows me to take one of the names and use it to 'look up' the class property, select it an assign the value to it, looping through the name-value pairs to make all the assignments?
I didn't see a method attached to my Class as I defined it. Should I define my Class differently? I used the Class in the EF6 Code First method to create the backing database.
The alternative as I see it is to list each Class property one by one, looking up the name and assign the value, but that seems like a tedious way of doing things.
Just thought I would ask. Maybe there's a better way to do this.
Thanks.
Best Regards,
Alan
There are three classes which will help you; TypeDescriptor, PropertyDescriptor and PropertyDescriptorCollection. They are all located in the System.ComponentModel namespace.
Imports System.ComponentModel
We'll be using the following class for this example:
Public Class Foo
'Implements ICustomTypeDescriptor (Optional)
Public Property A() As String
Public Property B() As Date
Public Property C() As Integer
Public Property D() As Boolean
Public Overrides Function ToString() As String
Return String.Format("A='{0}', B='{1}', C='{2}', D='{3}'", Me.A, Me.B, Me.C, Me.D)
End Function
End Class
Get all the properties by invoking the static method GetProperties of the TypeDescriptor class. It returns a collection of PropertyDescriptor classes - your properties. Then you simply invoke either the SetValue and/or GetValue methods. Note that you can implement a custom type descriptor by implementing the ICustomTypeDescriptor interface.
Private Sub RunTest()
Dim properties As PropertyDescriptorCollection = TypeDescriptor.GetProperties(GetType(Foo))
Dim ignoreCase As Boolean = True
Dim foo1 As New Foo()
properties.Find("A", ignoreCase).SetValue(foo1, "hello")
properties.Find("B", ignoreCase).SetValue(foo1, Date.Now)
properties.Find("C", ignoreCase).SetValue(foo1, 1234I)
properties.Find("D", ignoreCase).SetValue(foo1, True)
'Get property value:
'Dim a As String = CType(properties.Find("A", ignoreCase).GetValue(foo1), String)
Debug.WriteLine(foo1.ToString())
End Sub
Output: (immediate window)
A='hello', B='30.11.2014 11:14:39', C='1234', D='True'
Extension
To expand this even further one can create some extension methods.
Imports System.Runtime.CompilerServices
Public Module Extensions
<Extension()>
Public Function GetProperty(Of TComponent)(component As TComponent, propertyName As String, Optional ByVal ignoreCase As Boolean = True) As Object
Return TypeDescriptor.GetProperties(GetType(TComponent)).Find(propertyName, ignoreCase).GetValue(component)
End Function
<Extension()>
Public Function GetProperty(Of TComponent, TValue)(component As TComponent, propertyName As String, Optional ByVal ignoreCase As Boolean = True) As TValue
Return CType(TypeDescriptor.GetProperties(GetType(TComponent)).Find(propertyName, ignoreCase).GetValue(component), TValue)
End Function
<Extension()>
Public Sub SetProperty(Of TComponent)(instance As TComponent, propertyName As String, value As Object, Optional ByVal ignoreCase As Boolean = True)
TypeDescriptor.GetProperties(GetType(TComponent)).Find(propertyName, ignoreCase).SetValue(instance, value)
End Sub
End Module
Now it's very easy to set/get a property value by name.
Private Sub RunTest()
Dim foo1 As New Foo()
foo1.SetProperty("A", "hello")
foo1.SetProperty("B", Date.Now)
foo1.SetProperty("C", 1234I)
foo1.SetProperty("D", True)
'Get property value:
'Dim a As String = CType(foo1.GetProperty("A"), String)
'Dim a As String = foo1.GetProperty(Of String)("B")
Debug.WriteLine(foo1.ToString())
End Sub
Output:
A='hello', B='30.11.2014 11:18:17', C='1234', D='True'

Call a private method using a String in VB.Net

ok I do have class like this.
Namespace mySpace
Public Class ClassA
Private Function MethodA(prm AS Boolean) As Boolean
Return False
End Function
Private Function MethodB() As Boolean
Return False
End Function
End Class
Public Class ClassB
Private Function MethodC() As Boolean
Return True
End Function
Private Function InvokeA() As Boolean
Dim methodObj As MethodInfo
'null pointer except below here
methodObj = Type.GetType("mySpace.ClassA").GetMethod("MethodA")
Dim params As Boolean() = {False}
Return CBool(methodObj.Invoke(New ClassA(), params))
End Function
End Class
End Namespace
What I am trying here is to invoke a method from a different class with parameters using its method. But this returns a null pointer exception. Why? Where I went wrong?
You are doing various things wrong. The following code should work without any problem:
Dim objA As ClassA = New ClassA()
methodObj = objA.GetType().GetMethod("MethodA", BindingFlags.Instance Or BindingFlags.NonPublic)
Dim params As Object() = {False}
methodObj.Invoke(objA, params)
You have various errors which shouldn't allow your code to run at all, namely:
You are retrieving a private method without the adequate
BindingFlags.
You are not passing the arguments as Object type.
Additionally, you are not using GetMethod with an instantiated object of ClassA (e.g., objA above) and instance.GetType(); I am not 100% sure that you need to do that (perhaps you can accomplish it as you intend), but performing this step is a pretty quick process and would allow the code above to work without any problem.

Cast array in Object variable to type in System.Type variable

I have this function:
Public Sub DoStuff(ByVal type as System.Type, ByVal value as Object)
End Sub
The 'value' argument is always an array of the same type as 'type'. How can I loop through the values of the array?
I'd like to be able to do something like this:
DoStuff(GetType(Integer), New Integer(){1,2,3})
Public Sub DoStuff(ByVal type as System.Type, ByVal value as Object)
//Strongly types arr as Integer()
Dim arr = SomeCast(type, value)
For Each i in arr
//Do something with i
Next
End Sub
Edit Ok, I think I'll add more details so you can see what I'm trying to do. I have an object that captures values coming back from another page. Once I have them captured, I want to loop through the 'Values' property. So DoStuff() above would be called for each dictionary object in 'Values'. If the value in the dictionary objct is an array I want to loop through it as well. I was saving the type added through the generic function call as a System.Type, but maybe that's not the way to go. How can I write this so I can save the type of the array and loop through the array later?
Public Class PopUpReturnValues
Implements IPopUpReturnValues
Public Sub AddValue(Of T As Structure)(ByVal name As String, ByVal value As T) Implements IPopUpReturnValues.AddValue
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(T), .Value = value, .IsArray = False})
End Sub
Public Sub AddArray(Of T As Structure)(ByVal name As String, ByVal values As T()) Implements IPopUpReturnValues.AddArray
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(T), .Value = values, .IsArray = True})
End Sub
Public Sub AddStringValue(ByVal name As String, ByVal value As String) Implements IPopUpReturnValues.AddStringValue
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(String), .Value = value, .IsArray = False})
End Sub
Public Sub AddStringArray(ByVal name As String, ByVal values As String()) Implements IPopUpReturnValues.AddStringArray
_values.Add(name, New PopUpReturnValue() With {.UnderlyingType = GetType(String), .Value = values, .IsArray = True})
End Sub
Private _values As New Dictionary(Of String, PopUpReturnValue)
Public ReadOnly Property Values() As IDictionary(Of String, PopUpReturnValue)
Get
Return _values
End Get
End Property
Public Class PopUpReturnValue
Public UnderlyingType As Type
Public Value As Object
Public IsArray As Boolean
End Class
End Class
Comments moved to answers per OP
Your "do something" code in based on the type I assume, String vs Int vs Apple, it would need to handle all three types with an If statement. Just include an overload for those three types, you don't actually need to pass the type information. However, if its just calling ToString() then just use an Object array.
And if you don't like overloads, just use the TypeOf operator to inspect the values of the array. When you throw an Integer into an Object array, its still an Integer, just a boxed one.
Is the type known at compile time? If so, perhaps you could use Generics.
You can provide an Action, like this:
Public Sub DoStuff(ByVal value As Array, ByVal process As Action(Of Object) )
For Each item In value
process(item)
Next item
End Sub
Then you just need a method that takes one parameter for each of the types you care about and knows how to cast object to that type. Then call DoStuff() passing in the address of that method. You could even use a lambda if you wanted.

Generic List Equivalent of DataTable.Rows.Find using VB.NET?

I am converting DataTables to a generic list and need a quick and easy way to implement a Find function. It seems I am going to have to use a Predicate. Upon further investigation, I still can't seem to re-create the functionality. I have this predicate...
Private Function ByKey(ByVal Instance As MyClass) As Boolean
Return Instance.Key = "I NEED THIS COMPARISON TO BE DYNAMIC!"
End Function
And then calling it like this...
Dim Blah As MyClass = MyList.Find(AddressOf ByKey)
But I have no way to pass in a key variable to this predicate to do the comparison, as I used to do with DataTable...
Dim MyRow as DataRow = MyTable.Rows.Find(KeyVariable)
How can I setup a predicate delegate function in VB.NET to accomplish this?
Do not recommend LINQ or lambdas because this is question is regarding .NET version 2.0.
Just put your predicate in a class instance:
Public Class KeyMatcher
Public Sub New(ByVal KeyToMatch As String)
Me.KeyToMatch = KeyToMatch
End Sub
Private KeyToMatch As String
Public Function Predicate(ByVal Instance As MyClass) As Boolean
Return Instance.Key = KeyToMatch
End Function
End Class
and then:
Dim Blah As MyClass = MyList.Find(AddressOf New KeyMatcher("testKey").Predicate)
We can even get a little fancy and make this generic:
Public Interface IKeyed(Of KeyType)
Public Key As KeyType
End Interface
Public Class KeyMatcher(Of KeyType)
Public Sub New(ByVal KeyToMatch As KeyType)
Me.KeyToMatch = KeyToMatch
End Sub
Private KeyToMatch As KeyType
Public Function Predicate(ByVal Instance As IKeyed(Of KeyType)) As Boolean
Return Instance.Key = KeyToMatch
End Function
End Class
And then make your MyClass type implement the new IKeyed interface