How to mimic Java's Wildcard types in VB.net? - vb.net

I have an interface which I defined like this:
Public Interface ISomething(Of T)
' methods
End Interface
I now did an implementation:
Public Class ConcreteThing
Implements ISomething(of SomeClass)
' Implementation
End Class
I have multiple such concrete implementations, and want to have a function which returns any of them based on its parameters. In Java, I would do something like this:
public ISomething<?> getSomething(ParamType p) {
if(p.hasFoo()) return new ConcreteThing();
if(p.hasBar()) return new OtherConcreteThing();
throw new IllegalStateException("p neither has Foo nor Bar");
}
I already searched about this issue and found out that VB.net does not have wildcard types, so I tried:
Public Function GetSomething(p as ParamType) as ISomething(Of Object)
If p.HasFoo Then Return New ConcreteThing()
If p.HasBar Then Return New OtherConcreteThing()
Throw New InvalidOperationException("p neither has Foo nor Bar")
End Function
This compiles, but I get the warning: Runtime errors might occurr when converting 'Foo.ConcreteThing' to 'Foo.ISomething(Of Object)'.
When I try the following, as suggested in a similar question:
Public Function GetSomething(Of T)(p as ParamType) as ISomething(Of T)
If p.HasFoo Then Return New ConcreteThing()
If p.HasBar Then Return New OtherConcreteThing()
Throw New InvalidOperationException("p neither has Foo nor Bar")
End Function
the warning only changes to Runtime errors might occurr when converting 'Foo.ConcreteThing' to 'Foo.ISomething(Of T)'.
So, how do I get this right? Or, if this indeed IS right, how do I have Visual Studio ignore this warning?

I investigated on this issue a little more, discussed it with my colleagues, and I think I found the solution / reason for the warnings.
The warning message is a bit hard to understand and unconcise. What they are trying to say is that, as silly as it sounds, covariance does not work as expected for primitive types, even when using the Out keyword!
Consider an excerpt from this example on MSDN:
' Covariance.
Dim strings As IEnumerable(Of String) = New List(Of String)()
' An object that is instantiated with a more derived type argument
' is assigned to an object instantiated with a less derived type argument.
' Assignment compatibility is preserved.
Dim objects As IEnumerable(Of Object) = strings
This works. Now, change the first IEnumerable to IList:
Dim strings As IList(Of String) = New List(Of String)()
Dim objects As IEnumerable(Of Object) = strings
Works, too. OK, we are lucky, let's change the second:
Dim strings As IList(Of String) = New List(Of String)()
Dim objects As IList(Of Object) = strings
Boom, InvalidCastException. Looking at the signature, this is because the generic parameter in IEnumerable is defined as Of Out T, and IList is only defined As T.
Now, let's define our own.
Interface ISomething(Of Out T)
ReadOnly Property Value As T
End Interface
Class IntThing
Implements ISomething(Of Integer)
Public ReadOnly Property Value As Integer Implements ISomething(Of Integer).Value
Get
Return 42
End Get
End Property
End Class
Now, do this:
Dim s1 As ISomething(Of Integer) = new IntThing()
Works. Now add this:
Dim s2 As ISomething(Of Object) = s1
Boom, InvalidCastException. Now, the funniest part. Add a second implementation of ISomething:
Class StringThing
Implements ISomething(Of String)
Public ReadOnly Property Value As String Implements ISomething(Of String).Value
Get
Return "foo"
End Get
End Property
End Class
And do:
Dim s1 As ISomething(Of String) = New StringThing()
Dim s2 As ISomething(Of Object) = s1
This, on the other hand, works! So, let's go back to the List example.
Dim ints As IEnumerable(Of Integer) = New List(Of Integer)()
Dim objects As IEnumerable(Of Object) = ints
This will get you an InvalidCastException, too.
So, my conclusion is that covariance not only needs the Out keyword, it additionally only works with non-primitive types. .net seems to handle wrapper classes differently to the JVM.
So, never ignore this warning when it pops up. When it does, things will go wonky in an absolutely illogical way! That means, for what I want to achieve, going with simple Objects instead trying to find an equivalent for ISomething<?> is the way to go.
I only use this internally to read a binary file into a more convenient structure to extract the data I pass out via the API in the end, so using Object does not make things very much worse here.

It's weird, I don't get the warning like you do. But I do get an InvalidCastException if I try to run the code.
To get rid of the error (and hopefully your warning as well), you can make the generic type T on ISomething covariant.
Public Interface ISomething(Of Out T) ' Add the "Out" keyword here to make it covariant
' methods
End Interface
Then you should be able to use your GetSomething function as you had attempted:
Public Function GetSomething(p as ParamType) as ISomething(Of Object)
If p.HasFoo Then Return New ConcreteThing()
If p.HasBar Then Return New OtherConcreteThing()
Throw New InvalidOperationException("p neither has Foo nor Bar")
End Function
Relevant documentation: Covariance and Contravariance in Generics
Covariance
Enables you to use a more specific type than originally specified.
You can assign an instance of IEnumerable<Derived> (IEnumerable(Of Derived) in Visual Basic) to a variable of type IEnumerable<Base>.
And lower in the Defining Variant Generic Interfaces and Delegates section:
A covariant type parameter is marked with the out keyword (Out keyword in Visual Basic, + for the MSIL Assembler).

Related

Can't get a list of declared methods in a .net class

Having read a great many posts on using reflection to get a list of methods in a given class, I am still having trouble getting that list and need to ask for help. This is my current code:
Function GetClassMethods(ByVal theType As Type) As List(Of String)
Dim methodNames As New List(Of String)
For Each method In theType.GetMethods()
methodNames.Add(method.Name)
Next
Return methodNames
End Function
I call this method like this:
GetClassMethods(GetType(HomeController))
The return has 43 methods, but I only want the methods I wrote in the class. The image below shows the beginning of what was returned. My declared methods are in this list, but down at location 31-37. There are actually 9 declared methods, but this list doesn’t show the Private methods.
When I look at theType, I see the property I want. It is DeclaredMethods which shows every declared method, public and private.
However, I’m not able to access this property with a statement such as this.
Dim methodList = theType.DeclaredMethods
The returned error is that DelaredMethods is not a member of Type. So, my questions are multiple:
1) Most important, what code do I need to retrieve every declared method in the class, and only the methods I declared?
2) Why am I not able to access the property that gives the list of DeclaredMethods()?
Try this:
Function GetClassMethods(ByVal theType As Type) As List(Of String)
Dim flags = Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.DeclaredOnly
Dim result = theType.GetMethods(flags).
Where(Function(m) Not m.IsSpecialName).
Select(Function(m) m.Name)
Return result.ToList()
End Function
or for some fun with generics:
Function GetClassMethods(Of T)() As List(Of String)
Dim flags = Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.DeclaredOnly
Dim result = GetType(T).GetMethods(flags).
Where(Function(m) Not m.IsSpecialName).
Select(Function(m) m.Name)
Return result.ToList()
End Function
The IsSpecialName filter excludes methods with compiler-generated names, such as the special methods used by the compiler to implement properties. You can also play around more with the flags if you need to include, say, NonPublic members as well.
Finally, whenever you have a method ending with Return something.ToList() (or which could end with it, as my adaption shows here), it's almost always better to change the method to return an IEnumerable(Of T) instead, and let the calling code call ToList() if it really needs it. So my first example above is really better like this:
Function GetClassMethods(ByVal theType As Type) As IEnumerable(Of String)
Dim flags = Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.DeclaredOnly
Return theType.GetMethods(flags).
Where(Function(m) Not m.IsSpecialName).
Select(Function(m) m.Name)
End Function
Hey, that could be a single-liner. Then for those situations where you really need a list, you can do this:
Dim methodNames As List(Of String) = GetClassMethods(HomeController).ToList()
You'll start to find in many situations you don't need to use ToList() at all; the generic IEnumerable was good enough. Certainly this is true anywhere you just use the result with For Each loop. Now suddenly the memory use in your programs are significantly reduced.

.Where method not defined on generic typed list?

When I try to use the .Where() method on a list, this does method does not seem to be defined if the list is of a generic type:
In my program, I have a class called Warning, and in another class, a list of warnings, defined as:
Dim warningList As List(Of Warning)
When I try to manipulate this list as:
Dim item = warningList.Where(Function(x) x.GetName() = "Foo").FirstOrDefault()
This works completely fine, but when I try it like this:
Dim itemList
if(type = "Warning") Then 'Please note that this condition is true...
itemList = warningList
End If
Dim item = itemList.Where(Function(x) x.GetName() = "Foo").FirstOrDefault()
I get an exception, stating that method .Where() is not defined for class Warning
Can anybody tell me why this is?
Thank you!
Now that you've edited your question it's clear.
You declare itemList without a type, so it's Object implicitly(in VB.NET with option strict set to Off which i strongly recommend against).
Now that you have declared a variable of type Object you can asssign any type to it. But you would have to cast it back to its real type List(Of Warning) to be able to use list or LINQ methods(which extend IEnumerable(Of T).
But instead declare it with the correct type:
Dim itemList As List(Of Warning)
if(type = "Warning") Then
itemList = warningList
End If
Dim item = itemList.Where(Function(x) x.GetName() = "Foo").FirstOrDefault()
Including to comment to explain why Warning is not related to this problem:
That's not the real code. If warningList is really a List(Of Warning)
you should be able to use Enumerable.Where(if LINQ is
imported). The fact that you assign this instance to another variable
(on declaration) doesn't change anything because that variable's type
is also a List(Of Warning). So itemList.Where should work too. Warning
has nothing to do with it because the type which is extended by Where
is IEnumerable(Of T), T can be any type(even Object). Since List(Of T)
implements IEnumerable(Of T) you can use Enumerable.Where on any list
(or array).
If you actually have multiple types and Warning is just one of it, you should implement a common interface. Here's an example:
Public Enum NotificationType
Warning
Info
[Error]
End Enum
Public Interface INamedNotification
ReadOnly Property Type As NotificationType
Property Name As string
End Interface
Public Class Warning
Implements INamedNotification
Public Sub New( name As String )
Me.Name = name
End Sub
Public Property Name As String Implements INamedNotification.Name
Public ReadOnly Property Type As NotificationType Implements INamedNotification.Type
Get
Return NotificationType.Warning
End Get
End Property
End Class
Now you can declare a List(Of INamedNotification) and fill it with whatever implements this interface, like the Warning class:
Dim notificationList As List(Of INamedNotification)
if type = "Warning" Then
itemList = warningList
Else If type = "Info"
itemList = infoList
End If
Dim item = notificationList.Where(Function(x) x.Name = "Foo").FirstOrDefault()

So a VB interface can't have shared functions. Is there an alternative to creating dummy objects?

To avoid getting into the weeds on my particular program, let me just create a simplified case.
I have a generic class that should work on a variety of objects. Each of those objects must implement a certain interface.
What I WANT to say is something like:
Public Interface GenThing
Shared Function thing_name() As String ' This doesn't work! Can't be shared!
Sub FillOne(row As DataRow)
End Interface
public class Thing1
implements GenThing
public shared function thing_name() as string implements GenThing.thing_name
return "thing number one"
end function
public sub FillOne(row as DataRow) implements GenThing.MakeOne
... bunch of work ...
end sub
end class
public class ThingUtil(of T as {GenThing,New})
public function GetList(id as integer) as List(of T)
dim name=T.thing_name() ' This doesn't work!
dim ds as DataSet=GetData(name,id) ' bunch of work here that's the whole point of the class but not relevant to the question
dim my_list = new List(of T)
for each row as DataRow in ds.tables(0).rows
dim my_t = new T()
my_t.FillOne(row)
my_list.add(my_t)
next
return my_list
end function
end class
Do you get my problem? I need every class that implements the interface to have a function that returns a "name" that is used to get the data that is needed to create an instance of the object. But I need to know this name BEFORE I create the instance, because I need it to be able to create the instance. But VB doesn't allow an interface to have a shared function, so what I want to write doesn't work.
So what I've done is this:
I make thing_name not shared.
Then instead of simply "dim name=T.thing_name()", I write
dim dummy = new T()
dim name = dummy.thing_name()
Okay, it works, but it seems really ugly. I create an instance of the object, with all the overhead that that involves, just to get a piece of constant text.
Is there a better way? Or am I making a big deal out of nothing?
Update
I see that two people voted to close this question on the grounds that it is the same as "Why can't we have shared functions in an interface?"
I am not asking why I can't have a shared. I am saying, GIVEN that I can't, how do I solve this particular problem?
There's no really simple way of fixing this, no.
Depending on what thing_name does, however, you might approach things in a different way. If each implementation just returns a constant value, then it's effectively metadata about the class - and could be described in an attribute instead, which can be fetched at execution time. (See Type.GetCustomAttributes.) Unfortunately you can't then enforce all types implementing the interface to be decorated with the attribute - but you could write a unit test to check this pretty easily.
If thing_name needs to really do work at execution time, that's tougher. You could potentially look for a well-known shared method name instead and execute that via reflection (and again have unit tests to check that it's implemented properly).
I realize this is from a few years ago, but running into a similar problem, I wanted to offer a different solution. Pass a delegate as parameter to the ThingUtil constructor. You avoid having to put a shared method in an interface, and the constructor will force you to include the parameter at compile time.
You can add more delegates if needed, or to make it even simpler in this case, just pass name as a string instead of get_name as a delegate.
Define the delegate in the interface:
Public Interface GenThing
Delegate Function ThingNameDelegate() As String
Sub FillOne(row As DataRow)
End Interface
Public Class Thing1
Implements GenThing
Public Shared Function thing_name() As String 'name this whatever you want
Return "thing number one"
End Function
Public Sub FillOne(row As DataRow) Implements GenThing.FillOne
'do stuff
End Sub
End Class
In ThingUtil, add a member to store the delegate, a constructor parameter to to accept, and call it with .Invoke():
Public Class ThingUtil(Of T As {GenThing, New})
Private m_thing_name As GenThing.ThingNameDelegate
Public Sub New(thing_name As GenThing.ThingNameDelegate)
m_thing_name = thing_name
End Sub
Public Function GetList(id As Integer) As List(Of T)
Dim name = m_thing_name.Invoke()
Dim ds As DataSet = GetData(name, id) ' bunch of work here that's the whole point of the class but not relevant to the question
Dim my_list = New List(Of T)
For Each row As DataRow In ds.Tables(0).Rows
Dim my_t = New T()
my_t.FillOne(row)
my_list.Add(my_t)
Next
Return my_list
End Function
End Class
Finally, use it like this:
Dim tu as new ThingUtil(Of Thing1)(AddressOf Thing1.get_name)
tu.GetList(1)

Is it possible to add an interface to an existing type?

I've developed some serialization code for my types. They all share a common interface to facilitate the serialization logic. Would it be possible to also add that interface to some basic types like Integer or String so I could pass one of those basic type values into my serialization logic and have it work? I'm imagining something along the lines of extension methods, but adding an interface rather than a method? I'm sure I could come up with some way to do it using late-binding, but I'd like to avoid that, if possible.
No. It is not possible to extend an existing type to make it implement an interface (short of adding Implements IMyInterface to the top of the code for that type, that is). The closest thing to that would be to create a derived class which adds the interface to the base class. If you override the CType operator you could even make it so values could be seamlessly converted from one type to the other without explicitly casting them. However, since you mentioned String and Integer as the types that you want to extend, that is not even possible. You can't create a new type that inherits from String because String is defined as NotInheritable. Similarly, you can't create a new type that inherits from Integer because Integer is a Structure, not a Class. Structures do not support inheritance.
Therefore, the best option that you have would be to create a new class which wraps the core value, extends it by implementing the interface, and then overrides the CType operator to make it simple to convert between the core type and the wrapper type. For instance, let's say you had an interface like this:
Public Interface IWritable
Sub Write()
End Interface
And you had a method that took an argument of that type, like this:
Private Sub TestWrite(writableObject As IWritable)
writableObject.Write()
End Sub
If you needed to pass an Integer into that method, you could make a wrapper class like this:
Public Class WritableInteger
Implements IWritable
Public Sub New(value As Integer)
Me.Value = value
End Sub
Public Property Value As Integer
Public Sub Write() Implements IWritable.Write
Console.Write(Value)
End Sub
Overloads Shared Widening Operator CType(value As Integer) As WritableInteger
Return New WritableInteger(value)
End Operator
Overloads Shared Widening Operator CType(value As WritableInteger) As Integer
Return value.Value
End Operator
End Class
Since the CType operator is overloaded as Widening, that means that you can convert the value between the two types without casting (even with Option Strict On). For instance, this works:
Dim w As WritableInteger = New WritableInteger(5)
Dim i As Integer = w
w = i
Unfortunately, since TestWrite is asking for an IWritable rather than a WritableInteger, you can't just call TestWrite with an Integer, like this:
Dim i As Integer = 5
TestWrite(5) 'This won't work!
The compiler knows that it needs to convert the Integer to an IWritable object, but since any number of types may implement that interface, it doesn't automatically try to figure out if any of them provide a CType operator for that. Since there may be multiple types that allow widening conversions from Integer to IWritable, it just throws up its hands and cries fowl. Therefore, even though the widening conversion is declared, you still have to explicitly cast the type in a case like that. For instance:
Dim i As Integer = 5
TestWrite(CType(i, WritableInteger))
Or, perhaps more simply:
Dim i As Integer = 5
TestWrite(New WritableInteger(5))
You could make it more convenient by creating overloads for all the common types that will need to be wrapped. For instance, if you created an overload to the TestWrite method, like this:
Public Sub TestWrite(value As Integer)
TestWrite(New WritableInteger(value))
End Sub
Then you could easily call it like this:
TestWrite(5)
Converting back from an IWritable variable to an Integer, though, is even more difficult. For instance:
Dim w As IWritable = New WritableInteger(5)
Dim i As Integer = w ' This won't work!
Dim i2 As Integer = CType(w, Integer) ' Whis won't work either!
If you need to do that, you'd actually have to first cast it to a WritableInteger (and know that it is that type of object in the first place), for instance:
Dim w As IWritable = New WritableInteger(5)
If TypeOf w Is WritableInteger Then
Dim i As Integer = CType(w, WritableInteger)
End If
Unfortunately, there's really no way to make that any easier while still maintaining the safety of the compile-time type checking.
Finally it's also worth mentioning that, if you decide to make a wrapper like that, and the implementation of the interface is the same regardless of the wrapped type, then you could implement it as a generic type, like this:
Public Class Writable(Of T)
Implements IWritable
Public Sub New(value As T)
Me.Value = value
End Sub
Public Property Value As T
Public Sub Write() Implements IWritable.Write
Console.Write(Value)
End Sub
Overloads Shared Widening Operator CType(value As T) As Writable(Of T)
Return New Writable(Of T)(value)
End Operator
Overloads Shared Widening Operator CType(value As Writable(Of T)) As T
Return value.Value
End Operator
End Class
Then you could call the TestWrite method like this:
TestWrite(New Writable(Of Integer)(5))
TestWrite(New Writable(Of String)("Hello World"))

What is the VB.NET equivalent of List<?> in Java

I'm strictly looking for a language conversion here of this:
List<?>
I already have a work around I'm just surprised that I can't find the vb.net equivalent of a variable type of a "raw" generic list that takes a wild card.
Java's mechanism here enables you to reference a list regardless of what T is.
Java even enables you to go further and do things like:
List<? extends Number>
List<? super Double>
I tried:
List(Of )
and that did not work. That only seems to work inside the GetType function.
Those aren't C# generics declarations, they're Java declarations, so there is no equivalent in VB.NET.
As Konrad states in his comment above, Java and C# have quite different generic implementations. C# works by expansion, Java by erasure. Thus in C# there is no "raw" generic type available.
Are you talking about
List(of T)
List(of String)
etc.
See Generic Types in Visual Basic (Visual Basic) on MSDN. What you're looking for is called a Type Constraint. You'll have to combine that with a declaration on a method or class that performs the restriction that you want.
To accept any type you can do:
Public Sub testSub(Of t)(arg As List(Of t))
'Do something.
End Sub
Or to restrict it, for example, to a value type, you can do:
Public Sub testSub(Of t As Structure)(arg As List(Of t))
'Do something.
End Sub
What you're looking for is a purely generic property which isn't allowed in the .Net Framework. The code below will not compile.
Public Property testSub(Of t) As List(Of t)
Get
End Get
Set
End Set
End Property
The closest you can get is:
Public Class testClass(Of T)
Public Property testSub As List(Of t)
Get
End Get
Set
End Set
End Property
End Class
Ok my goal was to be able have a Collection property on MyBaseClass that points to it's collection, which is of type MyCollection(Of T), where T is a type of MyBaseClass. The base class does not know what will subclass it. It therefore needed to assign any implementation of MyCollection AND get the early binding and intellisense.
I needed to have my cake and eat it too (early binding and late binding).
Here's what I did inside MyBaseClass:
Public Event pCollectionChanged(Value As IList)
Private _pCollection As IList
<XmlIgnore()> _
<System.ComponentModel.Browsable(False)> _
Public Property pCollection As IList
Get
Return _pCollection
End Get
Set(Value As IList)
If _pCollection Is Value Then Exit Property
_pCollection = Value
RaiseEvent pCollectionChanged(Value)
End Set
End Property
Public Function Collection(Of t As MyBaseClass)() As MyCollection(Of t)
Return pCollection
End Function
Now I can assign a MyCollection(Of anything) to pCollection, and I can do this from calling code:
Dim MyObject As New MyBaseClassSubType
MyObject.Collection(Of MyBaseClassSubType).MyNeatoCollectionMethod
whereas before I just had Collection As IList, which did not give me access to MyNeatoCollectionMethod
Tada! Thanks to all those who answered.