How to implement LINQ functions in a VB.NET collection class - vb.net

I have a collection class which is a wrapper for a
List(Of MyClass)
And the collection class implements things like add, count etc
Private lst As List(Of MyClass)
Public Function Count() As Long
Return lst.Count
End Function
I want to add the ability to do Linq queries on the collection class. So client could do something like this:
dim c as New MyCollectionClass
c.Add(New MyClass With {.Name = "XXX"})
c.Add(New MyClass With {.Name = "XXX"})
c.Add(New MyClass With {.Name = "YYYY"})
Dim nc As MyCollectionClass = c.GroupBy(function(x) x.Name)
How do I implement the groupby function and all the other Linq functions (select, filter, distinct, orderby etc) ? I've implemented iQueryable in the class.
I'm confused on how to declare it and its parameters and implementation etc. I've tried a few things like
Public Function GroupBy(f As Func(Of MyClass)) As IEnumerable
Return lst.GroupBy(Function(x As MyClass) f(x))
End Function
But thats just a syntax error, and I'm just stuck now :) thanks
FWIW this is what I've done for Iqueryable:
Implements IEnumerable, IQueryable
Private lst As New List(Of MyClass)
Public ReadOnly Property Expression As Expression Implements IQueryable.Expression
Get
Return lst.AsQueryable.Expression
End Get
End Property
Public ReadOnly Property ElementType As Type Implements IQueryable.ElementType
Get
Return lst.AsQueryable.ElementType
End Get
End Property
Public ReadOnly Property Provider As IQueryProvider Implements IQueryable.Provider
Get
Return lst.AsQueryable.Provider
End Get
End Property

You don't need to re-implement Linq extensions (Select, GroupBy, etc.). You just need to implement IEnumerable(Of MyClass), then standard Linq extensions will work with your class automatically.
An example of IEnumerable(Of ...) implementation:
Public Class MyCollection
Implements IEnumerable(Of MyClass1)
Private lst As New List(Of MyClass1)
Public Function GetEnumerator() As IEnumerator(Of MyClass1) Implements IEnumerable(Of MyClass1).GetEnumerator
Return lst.GetEnumerator()
End Function
Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
Return DirectCast(lst, IEnumerable).GetEnumerator()
End Function
End Class
And usage:
Dim collection = New MyCollection
...
Dim count = collection.Count()

Related

Is there a way I can replace the 'Object' value type in this Visual Basic dictionary with something more precise?

I'm using a factory to select from several services that are implementing the same generic interface. I have the factory written like this:
Public Class JurisdictionServiceFactory
Private JurisdictionTypeMapper As Dictionary(Of String, Object)
Sub New()
JurisdictionTypeMapper = New Dictionary(Of String, Object)
JurisdictionTypeMapper.Add("CountryJurisdiction", ProjectGlobals.UnityContainer.Resolve(Of IJurisdictionService(Of CountryJurisdiction)))
JurisdictionTypeMapper.Add("StateJurisdiction", ProjectGlobals.UnityContainer.Resolve(Of IJurisdictionService(Of StateJurisdiction)))
JurisdictionTypeMapper.Add("CountyJurisdiction", ProjectGlobals.UnityContainer.Resolve(Of IJurisdictionService(Of CountyJurisdiction)))
JurisdictionTypeMapper.Add("CityJurisdiction", ProjectGlobals.UnityContainer.Resolve(Of IJurisdictionService(Of CityJurisdiction)))
JurisdictionTypeMapper.Add("OtherJurisdiction", ProjectGlobals.UnityContainer.Resolve(Of IJurisdictionService(Of OtherJurisdiction)))
End Sub
Public Function getJurisdictionService(type As String) As Object
Return JurisdictionTypeMapper(type)
End Function
End Class
I would like to replace 'Object' as the value type with something that would allow the compiler to know what methods exist on the object. For example, I want to be able to use autocomplete. I've tried doing this: Private JurisdictionTypeMapper As Dictionary(Of String, IJurisdictionService(Of )), but I just get a message: "Type expected".
This is IJurisdictionService:
Public Interface IJurisdictionService(Of t)
Inherits IDisposable
Function GetJurisdictionsByCompany(companyId As Integer) As List(Of t)
Sub AddJurisdiction(jurisdiction As t)
End Interface
This is an example implementation:
Public Class CountryJurisdictionService
Implements IJurisdictionService(Of CountryJurisdiction)
Private jurisdictionRepository As IRepository(Of CountryJurisdiction)
Public Sub New(jurisdictionRepository As IRepository(Of CountryJurisdiction))
Me.jurisdictionRepository = jurisdictionRepository
End Sub
Public Sub AddJurisdiction(jurisdiction As CountryJurisdiction) Implements IJurisdictionService(Of CountryJurisdiction).AddJurisdiction
jurisdictionRepository.Add(jurisdiction)
jurisdictionRepository.Commit()
End Sub
Public Function GetJurisdictionsByCompany(companyId As Integer) As List(Of CountryJurisdiction) Implements IJurisdictionService(Of CountryJurisdiction).GetJurisdictionsByCompany
Return jurisdictionRepository.GetMany(Function(j) j.CompanyID = companyId, False)
End Function
End Class
Edit:
This is the context that the factory will be used in:
Public Sub AddJurisdiction(jurisdiction)
Using jurisdictionService = jurisdictionServiceFactory.getJurisdictionService(TypeName(jurisdiction))
jurisdictionService.AddJurisdiction(jurisdiction)
End Using
End Sub
Who is calling getJurisdictionService? As long as the caller knows what Type they are requesting you can do something like this.
How about changing this:
Public Function getJurisdictionService(type As String) As Object
Return JurisdictionTypeMapper(type)
End Function
To this:
Public Function getJurisdictionService(Of T)() As IJurisdictionService(Of T)
Return DirectCast(JurisdictionTypeMapper(GetType(T)), IJurisdictionService(Of T))
End Function
And the the caller does this to get a strongly-typed service:
service = jurisdictionServiceFactory.getJurisdictionService(Of CountryJurisdictionService)()
And of course the dictionary needs to be keyed off of the Type instead of the class name:
Private JurisdictionTypeMapper As Dictionary(Of Type, Object)
Edit:
Given the new details you have provided, in that situation I often like to create base class or parent interface to create a sibling relationship between the generic classes, even if this is just for a bit of code-based documentation:
Public Interface IJurisdictionService
Inherits IDisposable
End Interface
Public Interface IJurisdictionService(Of t)
Inherits IJurisdictionService
Function GetJurisdictionsByCompany(companyId As Integer) As List(Of t)
Sub AddJurisdiction(jurisdiction As t)
End Interface
And that goes for the Jurisdiction objects as well, they could use a parent Interface or base class:
Public Interface IJurisdictionService
End Interface
Then Private JurisdictionTypeMapper As Dictionary(Of String, Object) becomes Private JurisdictionTypeMapper As Dictionary(Of String, IJurisdictionService)

Vb.NET - GetEnumerator on a Custom Template class

I've got a generic class build like this
Public Class TabellaCustom(Of myType, TValue) Implements IEnumerable(Of TValue)
Private mKey As myType
Private mContenuto As TValue
...
Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of TValue) Implements System.Collections.Generic.IEnumerable(Of TValue).GetEnumerator
Return DirectCast(mContenuto, IEnumerator(Of TValue))
End Function
when I do something like this
dim Color as new ColorsEnumerable
Dim test(0) As StampeCommonFunctions.TabellaCustom(Of Color, String)
test(0) = New StampeCommonFunctions.TabellaCustom(Of Color, String)(Color.Red, "Red")
test.GetEnumerator()
I got an error:
Unable to cast object of type 'System.String' to type 'System.Collections.Generic.IEnumerator`1[System.String]'.
How can I solve this error? I must specify the type of object inside the class?
Well, mContenuto is a string, and you're trying to cast it into an IEnumerator(Of String), but the string class does not implement IEnumerator(Of String).
That's what the exception is telling you.
Your class seems to just hold two values (mKey, mContenuto), why do you want to implement IEnumerable<T> at all? It seems there's no need to this...
You could nonetheless implement GetEnumerator like this:
Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of TValue) Implements System.Collections.Generic.IEnumerable(Of TValue).GetEnumerator
Return {Me.mContenuto}.AsEnumerable().GetEnumerator()
End Function
Private Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return GetEnumerator()
End Function
This works by creating an single-element array out of mContenuto and returning its Enumerator.

VB Generics "cannot be converted"

For the problem I have these classes:
Public MustInherit Class BaseLeaf(Of T)
Implements IBaseLeaf
// etc
Public Class WebsiteLeaf
Inherits BaseLeaf(Of Headline)
// etc
Public Class WebsiteCollection
Inherits BaseCollection(Of WebsiteLeaf)
// etc
Public Class SubscriptionList
Private mCollection As BaseCollection(Of IBaseLeaf)
Public Sub LoadSubscriptions(ByVal collection As BaseCollection(Of IBaseLeaf))
mCollection = collection
End Sub
In the Main class I am trying to call the following function:
Private Sub FetchSubscriptions(ByVal websites As WebsiteCollection)
gUser.SubscriptionList.LoadSubscriptions(websites)
// code
End Sub
This will access LoadSubscriptions with passing the "websites" variable.
As you can see LoadSubscriptions expects a BaseCollection(Of IBaseLeaf).
The "websites" variable is a WebsiteCollection, which is a BaseCollection(Of WebsiteLeaf(Of Headline)).
Now I am getting the error: Value of type 'WebsiteCollection' cannot be converted to 'BaseCollection(Of IBaseLeaf)'.
What I am doing wrong here?
If class B is a descendent of class A this does not mean that Collection(Of B) is a descendant of Collection(Of A)!
If you have these definitions
Dim stringList As List(Of String)
Public Sub DoSomthingWithList(list As List(Of object))
list.Add(Date.Now)
End Sub
and you could call the method like this
DoSomthingWithList(stringList)
then the method would try to add a Date to the list which is actually a list of strings! Since the parameter is typed as object the date would be boxed (i.e. converted to an object) but not converted to string.
Therefore the collections List(Of X) and List(Of Y) are never compatible, even if Y inherits X.
Let's look at another example
Public Class TestClass
Implements IBaseLeaf
// etc
What happens if you call LoadSubscriptions with a WebsiteCollection?
Public Sub LoadSubscriptions(ByVal collection As BaseCollection(Of IBaseLeaf))
collection.Add(New TestClass())
End Sub

How to setup return value for a readonly property using RhinoMocks in VB.NET?

I'm using RhinoMock in VB.NET and I need to set the return value for a readonly list.
Here's what I want to do (but doesn't work):
dim s = Rhino.Mocks.MockRepository.GenerateStub(of IUserDto)()
s.Id = guid.NewGuid
s.Name = "Stubbed name"
s.Posts = new List(of IPost)
It fails on the compile because Posts is a readonly property.
Then I tried a lambda expression, which works fine for Function calls, but not so much for Properties. This fails to compile.
s.Stub(Function(x As IUserDto) x.Posts).Return(New List(Of IPost))
Next (failing) attempt was to use SetupResults, but this failed stating that it cannot be used in Playback mode.
Rhino.Mocks.SetupResult.For(s.Posts).Return(New List(Of IPost))
Which brings me back to my question:
How do I setup a return value for a readonly property using RhinoMocks in VB.NET?
Is IUserDto an interface? If it is then it should just work. If it isn't, then the problem could be that the readonly property in question is not overridable. RhinoMocks can only mock properties/methods which are defined in an interface or can be overridden.
Here is my (clumsy) attempt at proving that the lambda syntax should work:
Imports Rhino.Mocks
Public Class Class1
Public Sub Test()
Dim s = MockRepository.GenerateMock(Of IClass)()
Dim newList As New List(Of Integer)
newList.Add(10)
s.Stub(Function(x As IClass) x.Field).Return(newList)
MsgBox(s.Field(0))
End Sub
End Class
Public Class AnotherClass
Implements IClass
Public ReadOnly Property Field() As List(Of Integer) Implements IClass.Field
Get
Return New List(Of Integer)
End Get
End Property
End Class
Public Interface IClass
ReadOnly Property Field() As List(Of Integer)
End Interface
i.e. I would get a message box with the number 10 displayed on it (I didn't bother to try hooking up a unit-testing framework with this but that shouldn't make a difference) when Class1.Test is invoked.
Hope that helps (it was an interesting exercise trying to work with RhinoMocks in VB.NET in anycase).

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