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

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)

Related

Shared function that infers the class type and can be returned in a list

I would like to create a shared function that returns a list of instances of the classes type. Currently this is what my code looks like
class MyClass
Implements BusinessObject
Shared Function LoadAll(Of T As {BusinessObject, New})() As IEnumerable(Of T)
Dim helper = New SQLHelper()
Return helper.LoadDataTableFromDatabase("LoadTable", LoadAllProcedureName).Rows.Cast(Of DataRow).Select(Function(s) New T().FillDataRow(Of T)(s))
End Function
End Class
class MyDerivedClass Implements MyClass
End MyClass
When I go to use it, I have to use it like this:
MyDerivedClass.LoadAll(Of MyDerivedClass)()
I would like to be able to infer the type, instead of having to use the (Of MyDerivedClass) so that my code looks like MyDerivedClass.LoadAll().
Any help or keywords that I am missing to achieve this would be greatly appreciated.
Here is an extension method which (theoretically) would work on any class you define:
Public Module Module1
<Extension()> _
Public Function LoadAll(Of T As {BusinessObject, New})(ByVal x As T) As IEnumerable(Of T)
Dim LoadAllProcedureName As String = "LoadAllProcedure"
Dim helper = New SQLHelper()
Return helper.LoadDataTableFromDatabase("LoadTable", LoadAllProcedureName).Rows.Cast(Of DataRow).Select(Function(s) New T().FillDataRow(Of T)(s))
End Function
Public Sub Main()
Dim dC As New DerivedClass()
Dim allDc As IEnumerable(Of DerivedClass) = dC.LoadAll()
'::: Somewhat shorter syntax
Dim allDC As IEnumerable(Of DerivedClass) = (New DerivedClass()).LoadAll()
End Sub
End Module
But, as others have pointed out, this doesn't really clean anything up for you. More to the point, you are going to have to type (Of DerivedClass) in whatever variable you intend on populating with your enumerated DerivedClass, no?
And from what I can tell, you cannot have Shared extension methods -- should you be thinking that is the way to go.

Method doesnt want to move to Generic Class

I am wrapping a COM API.
In general, I have had good luck designing some generic classes and shoving the tested parts down into those classes.
Here is one that is giving me a problem.
There are classes that represent result sets. They do not inherit, they do implement a common interface, but it is a very simple interface. It does not expose the ResultSet functionality, specifically .COUNT or .GetAt(i)
My workaround is to make this a MustInherit and use CodeSmith to do the work for me. Not the end of the world. 13 more lines of generated code per entity.
I have played around with a class that might bridge this, and an interface that might bridge this, but I keep coming back to the fact that there is no common 'thing' in the API that represents a result set.
I may be missing something, I certainly am not seeing the solution.
The code for one instance of the work around is listed below
I would like to move this function to the Generic. It currently sits in each instance of class that uses the generic.
ICustomerRetList inherits from IBase. IBase has neither .Count or .GetAt() as mentioned above.
To be clear- My question is this : Can you suggest a vb construct that will allow me to move this function from my concrete class, down to my generic class
Public Overrides Function RetListToList(RetList As ICustomerRetList) As List(Of Customer)
Dim oItem As ICustomerRet
Dim oItem As Customer
Dim l As New List(Of Customer)
For idx = 0 To RetList.**Count** - 1 '.Count is not a member of IBase
oqbItem = RetList.**GetAt**(idx) '.GetAt() is not a member of IBase
oItem = New Customer()
'add the Item to the list
Call l.Add(oItem)
Next
Return l
End Function
If all implementations of IBase have these methods, and they all have the same names, you could combine extension methods and reflection to effectively lower the functions.
Public Class CustomerRetListExtensions
<Extension()>
Public Function GetAt(ByVal list As IBase, ByVal idx As Integer) As IBase
Return DirectCast(list.GetType().GetMethod("GetAt").Invoke(list, New Object() { idx }), IBase)
End Function
' If Count is a property, otherwise use the same approach as for GetAt
<Extension()>
Public Function Count(ByVal list As IBase) As Integer
Return DirectCast(list.GetType().GetProperty("Count").GetValue(list), Integer)
End Function
End Class

How to mimic Java's Wildcard types in 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).

Building an ArrayList in an external class - vb.net

I have an ArrayList defined in Class A. Then I want to build this array in Class B and use it in Class A.
I defined the ArrayList as:
Public arrayList As ArrayList
Then, in Class B I do:
Dim trLogkEmpty As New A
'Loop with strEspece definition
trLogkEmpty.arrayList.Add(strEspece)
'End Loop
The program throws me this error:
NullReferenceException
I don't know why, because strEspece has never become null (I tested it). I don't know if there is another reason.
Also, when I loop through the arrayList elements in Class A, I get again NullReferenceException. This is the loop code:
For Each logkNull In Me.arrayElemWithLogkEmpty
Console.WriteLine(logkNull)
Next
I don't know what happens with the first exception, but the code runs "correctly". In the second exception I guess that is something like I'm loosing the elements values of the array. I don't know how to solve it...any help? I accept different ways to solve it!
You are making two of the same mistake. A NullReferenceException means that you are attempting to access a property or method on an object that hasn't been instantiated yet. You are attempting to access both A and A.arrayList without first creating new instances of them.
So, instead of just:
trLogkEmpty.arrayList.Add(strEspece)
You should have:
Dim trLogkEmpty As New A()
trLogkEmpty.arrayList = New ArrayList()
trLogkEmpty.arrayList.Add(strEspece)
However, I must insist that you avoid ArrayList, and also that you avoid instantiating a public member of a class from outside that class. I would suggest using a strongly-typed collection class such as List(Of T), and having a read-only property in A's take care of its instantiation and visibility so the collection (not its contents) can't be modified outside of A:
Public Class A
Private _myList As IList(Of String)
Public ReadOnly Property MyList As IList(Of String)
Get
If _myList Is Nothing Then
_myList = New List(Of String)
End If
Return _myList
End Get
End Property
End Class
And now you have:
Dim trLogkEmpty As New A()
trLogkEmpty.MyList.Add(strEspece)
You're probably going to need to keep your instance of A around, so class B should probably look somewhat like:
Public Class B
Private _a As A
Public Sub New()
_a = New A()
End Sub
' ... your methods that use _a.MyList
End Class
I got it finally. When I initialised the array in class 'A' I forget to create an instance of ArrayList class, specifically, I forget to put New:
Public arrayElemWithLogkEmpty As New ArrayList
So, partly, #Blackwood was right!
Thank you all and forgive me for my basic knowledge about vb.net.

Split 'RockPaperScissors.Paper' string in vb.net to just retrieve 'Rock'

The following code
player2.getWeapon.ToString()
returns
'RockPaperScissors.Rock'
OR
'RockPaperScissors.Paper'
OR
'RockPaperScissors.Scissors'
I am just interested in the Class Name, i.e. Rock, Paper or Scissors. How can I achieve this?
Split was what came to my mind but not sure if its the best.
Yes, string.Split seems to be the appropriate way:
Dim tokens As String() = player2.getWeapon.ToString().Split("."c)
Dim className As String = tokens.Last() ' or simply tokens(1) without Linq
But if you can extend your Weapon class with a property that returns it directly i would do that.
ToString is just a regular overridable method defined by the base Object class. Therefore, you can easily override it to make it return whatever you want. For instance:
Public Class Rock
Public Overrides Function ToString() As String
Return "Rock"
End Function
End Class
Or, if you want to force your classes to implement it, you could do it in your base Weapon class, like this:
Public Class Weapon
Public Sub New(weaponName As String)
_weaponName = weaponName
End Sub
Private _weaponName As String
Public Overrides Function ToString() As String
Return _weaponName
End Function
End Class
Public Class Rock
Inherits Weapon
Public Sub New()
MyBase.New("Rock")
End Sub
End Class
Alternatively, if you don't want to require it in the constructor, you could add a protected MustOverride Function GetWeaponName() As String in your base class and then return that from ToString. Either way, the derived classes will be forced to provide the name so that it can be returned in ToString.
The reason I recommend doing it this way is because I suspect you are using this string as a description which is displayed to the user. If that's the case, it's not really the class name that you want, but a description of the weapon. So far, they just happen to be the same, but logically speaking, that's not alway going to be the case. For instance, if you had a class called SuperDuperRock, you aren't going to want to display it like that to the user. Rather, you'd want it to be properly formatted with spaces between the words.
If you really do not wish to use Split() you could use IndexOf (more performant)
Dim className as String
Dim weapon = player2.getWeapon.ToString()
Dim pos as Integer = weapon.IndexOf("."c)
if pos >= 0 Then
className = weapon.Substring(pos + 1)
End if