I have a question concerning sorting lists of classes in VB.Net.
It seems every subject which is discussing this kind of sorting is not really clear for me.
I have a class Language with the following variables:
- Lang as a string
- Knowledge as a integer
I have got a list containing a couple of language classes in it. How can I sort on the Lang variable (Alphabetically sort the language classes in the list)?
Greetings,
Implement IComparable on your class, then use Sort:
Private Class Language : Implements IComparable(Of Language)
Public Property Lang As String
Public Property Knowledge As Integer
Sub New(lang As String)
Me.Lang = lang
End Sub
Public Function CompareTo(other As Language) As Integer _
Implements IComparable(Of Language).CompareTo
Dim comp As Integer = Me.Lang.CompareTo(other.Lang)
'If comp = 0 Then Return Me.Knowledge.CompareTo(other.Knowledge)
Return comp
End Function
End Class
Sub Main()
Dim lst As New List(Of Language)
lst.Add(New Language("fr"))
lst.Add(New Language("en"))
lst.Add(New Language("de"))
lst.Sort()
End Sub
EDIT: Added a hint on how to sort by multiple properties.
This was answered in a previous StackOverflow question: Sort a List of Object in VB.NET
Use Sort along with a custom function to compare the Lang variable.
theList.Sort(Function(x, y) x.Lang.CompareTo(y.Lang))
Related
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.
i found few new style (for me) to "define" output from select query.
Private Enum Item
ID
Item
Description
End Enum
Private Class Item
Private ID as String
Private Item as String
Private Desc as String
End Class
I 'm thinking of using either one of them. by using class i does not need to re-cast the element type before i display. but Enum seems like easier to understand.
Anyone have some suggestion how to decide?
Enum members are numeric (usually integer, but can be long). But they are not variable and do not change at runtime. So your enum equates to:
Private Enum Item
ID = 0
Item = 1
Description = 2
End Enum
If you want Description to be a string, then a class is a better idea. Enums are used to reference or index something or limit/define a selection. Like:
Public Property Stooge As Stooges
Friend Enum Stooges
Larry
Moe
Curly
Shemp
CurlyJoe
End Enum
The Stooge Property must be one of those values. in code it will show you the text ("moe") but store and integer (1). users will be shown the text in drop downs etc.
You can associate a description with Enum constants:
Public Enum Stooges
<Description("Larry - Funny one")> Larry
<Description("Moe - 'Smart' One")> Moe
<Description("Curly - Sore One")> Curly
<Description("Shemp - One with bad haircut")> Shemp
<Description("CurlyJoe - Last one")> CurlyJoe
End Enum
To get the description for a single one:
Public Shared Function GetDescription(ByVal EnumConstant As [Enum]) As String
Dim fi As Reflection.FieldInfo =
EnumConstant.GetType().GetField(EnumConstant.ToString())
Dim attr() As DescriptionAttribute =
DirectCast(fi.GetCustomAttributes(GetType(DescriptionAttribute),
False), DescriptionAttribute())
If attr.Length > 0 Then
Return attr(0).Description
Else
Return EnumConstant.ToString() ' return enum name if no Descr
End If
End Function
Usage: str = enumHelper.GetDescription(Stooge.Moe) (enumHelper is the name of the calss where the static/shared function resides).
To get a String Array of all the descriptions:
Public Shared Function GetDescriptions(ByVal type As Type) As String()
Dim n As Integer = 0
Dim enumValues As Array
Try
enumValues = [Enum].GetValues(type)
Dim Descr(enumValues.Length - 1) As String
For Each value As [Enum] In enumValues
Descr(n) = GetDescription(value)
n += 1
Next
Return Descr
Catch ex As Exception
MessageBox.Show(ex.Message)
Return Nothing
End Try
End Function
Usage: Dim strEnum As String() = enumHelper.GetDescriptions(GetType(Stooges))
From your question, what you really mean is Struct vs Class. I would default to creating a class. The main reason to use a struct vs a class, is when you need value semantics -- assignment/parameters copies the bits, not a pointer. This is fairly rare in my experience. Unless you have a compelling reason (and you know the difference), go with a class.
OK, that question sounds maybe a little confusing so I'll try to explain it with an example.
Pretend you have an object like this:
Class Something
Private varX As New Integer
Private varY As New String
'[..with the associated property definitions..]
Public Sub New()
End Sub
End Class
And another with:
Class JsonObject
Inherits Dictionary(Of String, String)
Public Function MakeObject() As Object 'or maybe even somethingObject
Dim somethingObject As New Something()
For Each kvp As KeyValuePair(Of String, String) In Me
'Here should happen something to use the Key as varX or varY and the Value as value for the varX or varY
somethingObject.CallByName(Me, kvp.Key, vbGet) = kpv.Value
Next
return somethingObject
End Function
End Class
I've got the 'CallByMe()' function from a previous question of myself
CallByName works different from the way you are trying to use it. Look at the documentation, it will tell you that in this particular case the correct usage would be
CallByName(Me, kvp.Key, vbSet, kpv.Value)
However, the function CallByName is part of a VB library that isn’t supported on all devices (notably it isn’t included in the .NET Mobile framework) and consequently it’s better not to use it.
Using proper reflection is slightly more complicated but guaranteed to work on all platforms.
Dim t = GetType(Something)
Dim field = t.GetField(kvp.Key, BindingFlags.NonPublic Or BindingFlags.Instance)
field.SetValue(Me, kvp.Value)
I'm trying to categorize articles by stored keywords. I have a list of keywords for a category, and I want an article to get assigned a category that has the most keyword count.
For Each keyword As String In category.Keywords
category.tempCount += Regex.Matches(article.Item("title").InnerXml, Regex.Escape(keyword)).Count
category.tempCount += Regex.Matches(article.Item("description").InnerXml, Regex.Escape(keyword)).Count
Next
And this is done for each category, ran for each article. I'm trying to sort the list in order to tell which category is the best one for this article. However it is possible more than one category is the best, and that none of the categories fit. So running this did not help me:
Categories.Sort(
Function(article1 As ArticleCategory, article2 As ArticleCategory)
Return article1.tempCount.CompareTo(article2.tempCount)
End Function)
Maybe I'm doing this all wrong, but so far I think I'm on the right path. (I also have a default compare in the Category class, it just wasn't working either.)
I get an exception on the sorting most likely caused because they are not unique.
The exception I get is an InvalidOperationException: Failed to compare two elements in the array. That's with using the comparer I built in the ArticleClass
Imports System.Xml
Class ArticleCategory
Implements IComparer(Of ArticleCategory)
Public ReadOnly key As Int32
Public ReadOnly Name As String
Public ReadOnly Keywords As List(Of String)
Public tempCount As Integer = 0
Public Sub New(ByVal category As XmlElement)
key = System.Web.HttpUtility.UrlDecode(category.Item("ckey").InnerXml)
Name = System.Web.HttpUtility.UrlDecode(category.Item("name").InnerXml)
Dim tKeywords As Array = System.Web.HttpUtility.UrlDecode(category.Item("keywords").InnerXml).Split(",")
Dim nKeywords As New List(Of String)
For Each keyword As String In tKeywords
If Not keyword.Trim = "" Then
nKeywords.Add(keyword.Trim)
End If
Next
Keywords = nKeywords
End Sub
'This should be removed if your using my solution.
Public Function Compare(ByVal x As ArticleCategory, ByVal y As ArticleCategory) As Integer Implements System.Collections.Generic.IComparer(Of ArticleCategory).Compare
Return String.Compare(x.tempCount, y.tempCount)
End Function
End Class
You need to implement IComparable instead of IComparer.
IComparer would be implemented by the class performing the sorting (such as a List class) while IComparable would be implemented by the class being sorted.
For example:
Public Function CompareTo(other As ArticleCategory) As Integer Implements System.IComparable(Of ArticleCategory).CompareTo
Return Me.tempCount.CompareTo(other.tempCount)
End Function
The best solution I found was using the Microsoft LINQ (a query language for objects) it works very well and quickly produces the right result.
Dim bestCat As ArticleCategory
bestCat = (From cat In Categories
Order By cat.tempCount Descending, cat.Name
Select cat).First
Completing my solution:
For Each category As ArticleCategory In Categories
category.tempCount = 0
For Each keyword As String In category.Keywords
category.tempCount += Regex.Matches(System.Web.HttpUtility.UrlDecode(article.Item("title").InnerXml), Regex.Escape(keyword)).Count
category.tempCount += Regex.Matches(System.Web.HttpUtility.UrlDecode(article.Item("description").InnerXml), Regex.Escape(keyword)).Count
Next
Next
Dim bestCat As ArticleCategory
Try
bestCat = (From cat In Categories
Order By cat.tempCount Descending, cat.Name
Select cat).First
Catch ex As Exception
ReportStatus(ex.Message)
End Try
So this is my preferred method to do a sort or a query on a list object or an array. It produces the best result, in the fastest time without having to add the IComparer implementations to your class.
Check it out at Microsoft.com
Is it possible to search for an object by one of its properties in a Generic List?
Public Class Customer
Private _id As Integer
Private _name As String
Public Property ID() As Integer
Get
Return _id
End Get
Set
_id = value
End Set
End Property
Public Property Name() As String
Get
Return _name
End Get
Set
_name = value
End Set
End Property
Public Sub New(id As Integer, name As String)
_id = id
_name = name
End Sub
End Class
Then loading and searching
Dim list as new list(Of Customer)
list.Add(New Customer(1,"A")
list.Add(New Customer(2,"B")
How can I return customer object with id =1? Does this have to do with the "Predicate" in Generics?
Note: I am doing this in VB.NET.
Yes, this has everything to do with predicates :)
You want the Find(Of T) method. You need to pass in a predicate (which is a type of delegate in this case). How you construct that delegate depends on which version of VB you're using. If you're using VB9, you could use a lambda expression. (If you're using VB9 you might want to use LINQ instead of Find(Of T) in the first place, mind you.) The lambda expression form would be something like:
list.Find(function(c) c.ID = 1)
I'm not sure if VB8 supports anonymous methods in the same way that C# 2 does though. If you need to call this from VB8, I'll see what I can come up with. (I'm more of a C# person really :)
Generally you need to use predicates:
list.Add(New Customer(1, "A"))
list.Add(New Customer(2, "B"))
Private Function HasID1(ByVal c As Customer) As Boolean
Return (c.ID = 1)
End Function
Dim customerWithID1 As Customer = list.Find(AddressOf HasID1)
Or with inline methods:
Dim customerWithID1 As Customer = list.Find(Function(p) p.ID = 1)
You could also overload the equals method and then do a contains. like this
Dim list as new list(Of Customer)
list.Add(New Customer(1,"A")
list.Add(New Customer(2,"B")
list.contains(new customer(1,"A"))
the equals method then would look like this
public overrides function Equals(Obj as object) as Boolean
return Me.Id.Equals(ctype(Obj,Customer).Id
end Function
Not tested but it should be close enough.
If you are using .NET 3.5 this can be done with LINQ to Objects:
How to: Query an ArrayList with LINQ
If not, in .NET 2.0 you can use the Find method of the list.
The idea is that you will need to provide an method that return true if a property of your object satisfies a certain condition.