I'm trying to create a class thats holds two (or more) generic delegates as properties. This way I can pass the object to a method and use only one parameter on that method. The problem is that I get a warning to specify a type, but I don't know the type yet. The whole point is to defer type declaration until the object gets instantiated.
Here's some dummy code dummy code to show what I want to do.
Public Function Method1()
Dim _container as Container = new Container()
_container.Property1 = //Here create delegate with type string
_container.Property2 = //Here create delegate with type integer
Method3(_container)
End Function
Public Function Method2()
Dim _container as Container = new Container()
_container.Property1 = //Here create delegate with type Integer
_container.Property2 = //Here create delegate with type integer
Method3(_container)
End Function
Public Function Method3(container as Container)
//execute type specific code and the delegates
//Throw exception when type is not supported (yet)
end Function
public Class Container
Property Property1 as MyDel(of T)
Property Property2 as MyDel(of T)
end Class
Public delegate function Mydel(of T)()
That's it. The point of this being that when adding new information/functionality to the system it is easy to create a new method called Method4, which uses arguments as needed (and of course create the method that will be executed as delegate).
I would like to put two delegates in one class because their is a connection between them and things belonging together should be put together in one class; just like a Person class can hold a name and address.
This code doesn't work, because Container needs a Type T, which means Method3 needs a specific type, but i can't do that because then I can't call Method3 with different arguments as I showed. I tried using a wrapper class, but a type specification is needed all the time.
I know you can do this
Public Property TestProp() As [Delegate]
Get
End Get
Set(ByVal Value As [Delegate])
End Set
End Property
[source:http://www.xtremedotnettalk.com/showthread.php?t=96800]
But that is not generic. What am I missing?
BTW, this is an optimization for code calling Method3 with two arguments. That seemed to work fine. Then I thought trying to use only one argument, which would make the system easier to understand.
My old code looks something like this:
Public function OldMethod1()
Dim del1 as Mydel(of Integer) = AddressOf SomeMethod
Dim del2 as MyDel(of String) = AddressOf SomeOtherMethod
oldMethod3(del1, del2)
end Function
Public function OldMethod2()
Dim del1 as Mydel(of String) = AddressOf AnOtherMethod
Dim del2 as MyDel(of String) = AddressOf AgainSomeOtherMethod
oldMethod4(del1, del2)
end Function
Public Function oldMethod3(del1 as Mydel(of Integer), del2 as Mydel(of string))
//execute delegates
end Function
Public Function oldMethod4(del1 as Mydel(of string), del2 as Mydel(of string))
//execute delegates
end Function
When reading this code I saw that it is only the type of the parameter the determines the execution flow. So if you can determine the subtype of the generic you know what to do. That seemed to be possible (use typeOf and or GetType), so the next thing to do is to create a generic parameter object that could be a substitute for 'del1' (I now realize I took it even took one step further and created the Container class to hold both the parameters).
Any answer in C# or VB.net will do. I use both.
The question is a little confusing, so this may not be an answer, but it's too long for a comment! I think what you want to do is use Action(Of T) as the type for Container.Property1 and Container.Property2.
Something like this (this will run in LINQPad using Language: VB Program):
Sub Main
Dim c1 = New Container(Of String, String)()
c1.Property1 = AddressOf Method1
c1.Property2 = AddressOf Method1
Method3(c1)
Dim c2 = New Container(Of String, Integer)()
c2.Property1 = AddressOf Method1
c2.Property2 = AddressOf Method2
Method3(c2)
End Sub
Public Sub Method1(x As String)
Console.WriteLine("Method1: {0}", x)
End Sub
Public Sub Method2(x As Integer)
Console.WriteLine("Method2: {0}", x)
End Sub
Public Sub Method3(Of T1, T2)(container as Container(Of T1, T2))
Console.WriteLine("Method3 got {0}, {1}", GetType(T1), GetType(T2))
' Not sure how you would actually call the delegates
If GetType(T1) = GetType(String) Then
container.Property1.DynamicInvoke("Hello")
ElseIf GetType(T1) = GetType(Integer) Then
container.Property1.DynamicInvoke(123)
End If
If GetType(T2) = GetType(String) Then
container.Property2.DynamicInvoke("World")
ElseIf GetType(T2) = GetType(Integer) Then
container.Property2.DynamicInvoke(456)
End If
End Sub
Public Class Container(Of T1, T2)
Public Property Property1 As Action(Of T1)
Public Property Property2 As Action(Of T2)
End Class
This produces the following results:
Method3 got System.String, System.String
Method1: Hello
Method1: World
Method3 got System.String, System.Int32
Method1: Hello
Method2: 456
Related
As per this answer to Unit testing private methods in C# I am using a PrivateObject to unit test private methods. This generally works really well and is quite easy to use, however it doesn't appear to work if the method is Shared or Static.
I don't really want to make the method public (otherwise I wouldn't even be bothering with PrivateObjects but I'm failing to see another way.
Example vb methods in class being tested:
Private Sub SampleInstanceMethod(arg as Object)
'Do something
End Sub
Private Shared Sub SampleSharedMethod(arg as Object)
'Do something
End Sub
Unit test code
Dim fooBarPO = New PrivateObject(GetType(FooBar))
fooBarPO.Invoke("SampleInstanceMethod", {arg1}) ' Works
fooBarPO.Invoke("SampleSharedMethod", {arg1}) ' Doesn't work
Interested in answers in either C# or VB
You could use PrivateType and InvokeStatic for this:
Dim foo = New PrivateType(GetType(TestClass))
foo.InvokeStatic("SampleSharedMethod", {arg1})
If you want to pass parameters ByRef - for instance if the method under test looked something like this:
Private Shared Sub SampleSharedMethod(ByRef arg As String)
arg += "abc"
End Sub
You can use this overload of InvokeStatic to get the results back:
Dim foo = New PrivateType(GetType(TestClass))
Dim params() As Object = {"123"}
foo.InvokeStatic("SampleSharedMethod", params)
Dim updatedValue = params(0) ' This would be 123abc in this example
I have a method that accepts Enum values as parameter, this answer is my reference. However I cant seem to make it work.
Enums
Public Enum FieldTypes
db_Alpha = 0
db_Memo = 1
db_Numeric = 2
End Enum
Public Enum SubTypes
st_None = 0
st_Phone = 35
st_Percentage = 37
st_Address = 63
st_Link = 66
End Enum
Method
Public Sub Foo(Of T)(ByVal Param1 As String, ByVal EnumParam As T)
Dim param As Type = GetType(T)
If param.IsEnum Then
Dim x = param.GetEnumValues()
End If
'Running some codes here
End Sub
Main
Public Sub Test()
Foo("TestName",FieldTypes.db_Memo)
End Sub
Im getting all the values of the type of Enum that was passed as a parameter.
I would want to reduce Method overloading and learn about generics here. Accepting C# and VB.net suggestions.
How could I get the value of of the Enum item passed?
How can I restrict this, that only the two types of Enum written is accepted?
Since an Enum is always a value type you can restrict your function "Foo" to only accept value types.
Public Sub Foo(Of T As Structure)(ByVal Param1 As String, ByVal EnumParam As T)
You could also restrict your generic function to only accept reference types
Public Sub Test(Of T As Class)(ByVal param As T)
or only accept types that implement a specific interface
Public Sub Test(Of T As IDispose)(ByVal param As T)
You can even concatenate those conditions
Public Sub Test(Of T As Class, IDispose, IEnumerable)(ByVal param As T)
Inside your function "Foo" you can check, that T is of the correct type like this:
If Not GetType(T).Equals(GetType(FieldTypes)) AndAlso _
Not GetType(T).Equals(GetType(SubTypes)) Then
Throw New Exception("Enum type not supported")
End If
To get the numeric value of "EnumParam" you can convert it to Integer or whatever else numeric type your enumeration is based on:
Dim x As Integer = Convert.ToInt32(EnumParam)
(I am a ASP.NET programmer that uses mostly VB but can also program in C#)
I like to have a list(t) full with keywords and function names.
For example:
Dim MyList as new List(of MyFunctions)
MyList.add(new Myfunction("descr","DBGetDescription()"))
MyList.add(new Myfunction("Name","DBGetName()"))
MyList.add(new Myfunction("create","DBGetCreateTS()"))
Public Class Myfunctions
Public code as String
Public function as String
Public Sub new(in_code as String, in_function as String)
code = in_code
function = in_function
End Sub
End Class
'what do i want to search for:
Dim ToDo as String = "descr"
'search for it
Dim ThisFunction as MyFunction = MyList.find(function(x) x.code = ToDo)
'answer in 'thisfunction' gives me the name of the function i need to use
Dim FunctionIWantToUse = ThisFunction.function
(the DBGetDescription, DBGetName and DBGetCreateTS are existing routines)
I know the name of the function(routine) I want to start but it is inside a variable.
Is it possible in VB (or in C#) to start the function?
Is there a work around?
M.
You can define a delegate which is basically a reference to a function. This allows you to map "names" to actual functions, to make it possible to call a function from a string name.
Delegate Function MyDelegateType() as string
Sub Main
' Create a dictionary mapping string names to the corresponding function.'
' Ive used a dictionary in my sample code, as its more straightforward, '
' but it would be trivial to wrap the name/function tuple up in a class and use a list'
Dim MyList as new Dictionary(of string, MyDelegateType)
MyList.Add("descr", addressof DBGetDescription)
MyList.add("Name", addressof DBGetName)
MyList.add("create", addressof DBGetCreateTS)
' Get the function for a key, and call it.'
Console.WriteLine(MyList.Item("descr")())
End Sub
Function DBGetDescription() as string
return "MyDescription"
End Function
Function DBGetName() as string
return "MyName"
End Function
Function DBGetCreateTS() as string
return "MyCreateTS"
End Function
I'm not entirely sure that my wording is correct on the title above. I guess the easiest way to explain it is to put the code below for reference and then give what I'd like to have as the expected behavior.
Public Class Domino
Public ReadOnly MinimumSideValue As Integer = 0
Public ReadOnly MaximumSideValue As Integer = 6
Public Property key As Integer
Public Property Side1 As Integer
Public Property Side2 As Integer
Public Sub New(side1Value As Integer, side2Value As Integer)
Me.Side1 = side1Value
Me.Side2 = side2Value
' Key should be a two digit number. side values 5 and 1 -> 51
Me.key = Integer.Parse(Side1.ToString & Side2.ToString)
End Sub
Public Overrides Function ToString() As String
Return Convert.ToString(Me.key)
End Function
End Class
Public Class DominoCollection
Inherits System.Collections.CollectionBase
Public Sub AddDomino(newDomino As Domino)
' Not sure if I should be calling Contains on the Protected List object
If Not List.Contains(newDomino) Then
List.Add(newDomino)
End If
End Sub
Public Function Contains(objDomino as Domino) As Boolean
' Need to check if a domino that has the same key already exists
Dim found As Boolean = False
For i = 0 To List.Count - 1 'This will fail if the list is empty...
If DirectCast(List.Item(i), Domino).key = objDomino.key Then found = True
Next
Return found
End Function
End Class
The fist code listing is the class that will have multiple instances created. The second class is the collection that will contain instances of the first class. The collection should not contain duplicate items. So when I call the contains method on the built-in protected List object from the CollectionBase I'd like it to search the list of items looking for a duplicate with the same value in the key property of the first class.
I'm not entirely sure if I can override the List.Contains method because it's a protected object and/or if I should really be creating my own contains method. I tried creating my own .Contains method, but it fails if the list is empty.
EDIT
I know that my code isn't in C#. However since it's .NET, C# answers would help just as much as VB would.
EDIT: Revised Code
After seeing some of the solutions below I've got the following that so far is looking like it will work as I wanted. However I'm not sure I implemented the Item property correctly. I haven't tested it as of yet because I'm not to the point of being able to use it in the rest of the application, I'm just trying to get a framework down.
Friend Class DominoCollection
Private domCol As Dictionary(Of Integer, Domino)
Public ReadOnly Property Keys As System.Collections.Generic.Dictionary(Of Integer, Domino).KeyCollection
Get
Return domCol.Keys
End Get
End Property
Public ReadOnly Property Values As System.Collections.Generic.Dictionary(Of Integer, Domino).ValueCollection
Get
Return domCol.Values
End Get
End Property
Default Public Property Item(DominoKey As Integer) As Domino
Get
If domCol.ContainsKey(DominoKey) Then
Return domCol.Item(DominoKey)
Else
Throw New KeyNotFoundException(String.Format("Cannot find key {0} in internal collection"))
End If
End Get
Set(value As Domino)
If domCol.ContainsKey(DominoKey) Then
domCol.Item(DominoKey) = value
Else
Throw New KeyNotFoundException(String.Format("Cannot find key {0} in internal collection"))
End If
End Set
End Property
Default Public Property Item(domino As Domino) As Domino
Get
If domCol.ContainsKey(domino.key) Then
Return domCol.Item(domino.key)
Else
Throw New KeyNotFoundException(String.Format("Cannot find key {0} in internal collection"))
End If
End Get
Set(value As Domino)
If domCol.ContainsKey(domino.key) Then
domCol.Item(domino.key) = value
Else
Throw New KeyNotFoundException(String.Format("Cannot find key {0} in internal collection"))
End If
End Set
End Property
Public Sub New()
domCol = New Dictionary(Of Integer, Domino)
End Sub
Public Sub Add(dom As Domino)
If Not domCol.ContainsKey(dom.key) Then
domCol.Add(dom.key, dom)
End If
End Sub
Public Function Contains(dom As Domino) As Boolean
Return domCol.ContainsKey(dom.key)
End Function
' flexibility:
Public Function Remove(dom As Domino) As Boolean
If domCol.ContainsKey(dom.key) Then
domCol.Remove(dom.key)
Return True
Else
Return False
End If
End Function
Public Function Remove(key As Integer) As Boolean
If domCol.ContainsKey(key) Then
domCol.Remove(key)
Return True
Else
Return False
End If
End Function
Public Function Count() As Integer
Return domCol.Count
End Function
Public Sub Clear()
domCol.Clear()
End Sub
End Class
Given the central importance of Domino.Key, I think a class which uses a Dictionary would make things easiest. Since your code is always looking for that to decide if a Domino object exists etc, a Dictionary will help detect that and prevent dupes etc.
Friend Class DominoCollection
Private mcol As Dictionary(Of Integer, Domino)
Public Sub New()
mcol = New Dictionary(Of Integer, Domino)
End Sub
Public Sub Add(dom As Domino)
If mcol.ContainsKey(dom.key) = False Then
mcol.Add(dom.key, dom)
' optional: replace - dont know if this is needed
Else
mcol(dom.key) = dom
End If
End Sub
Public Function Contains(dom As Domino) As Boolean
Return mcol.ContainsKey(dom.key)
End Function
' flexibility:
Public Function Remove(dom As Domino) As Boolean
If mcol.ContainsKey(dom.key) Then
mcol.Remove(dom.key)
Return True
End If
Return False
End Function
Public Function Remove(key As Integer) As Boolean
If mcol.ContainsKey(key) Then
mcol.Remove(key)
Return True
End If
Return False
End Function
End Class
For an Item and Items:
Public Function Item(key As Integer) As Domino
Return mcol(key)
End Function
Public Function Items() As List(Of Domino)
Return New List(Of Domino)(mcol.Values)
End Function
Items hides the KeyValuePair typically required to iterate and allows you to do a simple For/Each loop on the collection (of needed):
Dim doms As New DominoCollection
doms.Add(New Domino(1, 2))
doms.Add(New Domino(2, 3))
doms.Add(New Domino(4, 6))
For Each d As Domino In doms.Items
Console.WriteLine("s1:{0} s2:{1} k:{2}", d.Side1, d.Side2, d.key.ToString)
Next
Output
s1:1 s2:2 k:12
s1:2 s2:3 k:23
s1:4 s2:6 k:46
Yes, you definitely should either inherit from a generic collection class or implement a generic collection interface.
I'm not sure on the exact syntax for VB, but I think instead of List.Contains you want to use the Linq extension Any with a lambda. I think it goes something like this:
List.Any(Function(d as Domino) d.key = newDomino.key)
This will return true if any of the elements inside List matches the key.
I have the following code block in C#
private void Synchronize<T>(TextSelection selection, DependencyProperty property, Action<T> methodToCall)
{
object value = selection. GetPropertyValue(property) ;
if ( value != DependencyProperty. UnsetValue) methodToCall((T) value) ;
}
That I have converted to VB.
Private Sub Synchronize(Of T)(ByVal selection As TextSelection, ByVal [property] As DependencyProperty, ByVal methodToCall As Action(Of T))
Dim value As Object = selection.GetPropertyValue([property])
If value IsNot DependencyProperty.UnsetValue Then
methodToCall(DirectCast(value, T))
End If
End Sub
The calling method look like:
Synchronize(Of Double)(selection, TextBlock.FontSizeProperty, AddressOf SetFontSize)
Synchronize(Of FontWeight)(selection, TextBlock.FontSizeProperty, AddressOf SetFontWeight)
Synchronize(Of FontStyle)(selection, TextBlock.FontStyleProperty, AddressOf SetFontStyle)
Synchronize(Of FontFamily)(selection, TextBlock.FontFamilyProperty, AddressOf SetFontFamily)
Synchronize(Of TextDecorationCollection)(selection, TextBlock.TextDecorationsProperty, AddressOf SetTextDecoration)
My problem is with the DirectCast call; if my delegate argument can be a simple type (integer, double, etc) or an object. DirectCast doesn't like the simple data types an InvalidCastException is thrown when I try to cast to a double. Does anyone have a suggested solution to this problem? I've also tried TryCast, but it doesn't like my (of T) and says it must be class contstrained.
Thanks all!
Ryan
Try CType() instead of DirectCast().
Looking at the errors you're seeing, it sounds like you may need to add a constraint on your T handle limit it to a class in order for the TryCast to be used.
I'm not really familiar with VB's TryCast method (or DirectCast, for that matter), but something like this might help [note that (Of T) -> (Of T as Class) ]:
Private Sub Synchronize(Of T as Class)(ByVal selection As TextSelection, ByVal [property] As DependencyProperty, ByVal methodToCall As Action(Of T))
Dim value As Object = selection.GetPropertyValue([property])
If value IsNot DependencyProperty.UnsetValue Then
methodToCall(TryCast(value, T))
End If
End Sub
Use CType instead of TryCast/DirectCast. It should work like the casting in C# :
methodToCall(CType(value, T))
You can put multiple constraints on t
I believe the syntax is something like:
Private Sub Synchronize(Of T as {Class, Integer, Double})