How do you implement a IEqualityComparer<T> in VB.NET? - vb.net

I have the following function that loops through a directory and checks for a specified folder and file:
Private Function VerifyPath(ByVal root As String, ByVal folder As String, _
ByVal file As String) As Boolean
Dim folders As New List(Of String), files As New List(Of String)
Dim oDir As New IO.DirectoryInfo(root)
For Each dir As IO.DirectoryInfo In oDir.GetDirectories
folders.Add(dir.Name.ToLower)
Next
If folders.Contains(folder) Then
For Each item As IO.FileInfo In oDir.GetFiles
files.Add(item.Name.ToLower)
Next
If files.Contains(file) Then
Return True
End If
End If
Return False
End Function
The reason I did this method is so I could make sure that the items in each list and the passed file/folder were all lower case, otherwise I would have done something like this:
If oDir.GetDirectories.Contains( _
New IO.DirectoryInfo(String.Format("{0}\{1}", root, folder))) Then
If oDir.GetFiles.Contains( _
New IO.FileInfo(String.Format("{0}\{1}", root, file))) Then
Return True
End If
End If
Return False
My colleague mentioned something to me earlier about being able to ignore case by using a comparer. The .Contains extension can have a comparer argument along with the value. I did some searching on google and MSDN, and came up with the following comparer:
Public Class dirCompare
Implements IEqualityComparer(Of IO.DirectoryInfo)
Dim theCompare As CaseInsensitiveComparer
Sub New()
theCompare = CaseInsensitiveComparer.DefaultInvariant
End Sub
Sub New(ByVal culture As CultureInfo)
theCompare = New CaseInsensitiveComparer(culture)
End Sub
Public Function Equals1(ByVal x As System.IO.DirectoryInfo, ByVal y As System.IO.DirectoryInfo) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of System.IO.DirectoryInfo).Equals
If theCompare.Compare(x.name, y.name) = 0 Then
Return True
Else
Return False
End If
End Function
Public Function GetHashCode1(ByVal obj As System.IO.DirectoryInfo) As Integer Implements System.Collections.Generic.IEqualityComparer(Of System.IO.DirectoryInfo).GetHashCode
Return obj.ToString.ToLower.GetHashCode
End Function
End Class
When it gets to the theCompare(x.name, y.name) = 0 line, it errors out and this is the error message:
At least one object must implement IComparable.
Anyone know what this error means and how to go about correcting it?

Well you could implement a comparer, but that would be the hard way in this case. You have a couple other options available instead.
The first is that there is already a case-insensitive comparer you can use. There are a couple actually. Look in your intellisense prompts under System.StringComparer.
The second is that strings already have a built-in way to specify a case-insensitive compare:
Dim s As String = "a"
Dim t As String = "A"
If s.Equals(t, StringComparison.InvariantCultureIgnoreCase) Then
''//...
End If
And a third is that any searchPattern passed to Directory.GetFiles() or Directory.GetDirectories() is passed directly to the operating system, and Windows is only case-aware for file names, not case-sensitive. So you can pass your folder and file as a search pattern and do your lookup that way.

Related

Sort directory path alphabetically

Below code is used for sorting Directories in vb.net.
Dim a As New List(Of String)
a.Add("a\b\c")
a.Add("a\b\c\d")
a.Add("a\b\c d")
a.Add("a\b\c d\e")
a.Add("a\b\c\d\f")
a.Sort(Function(x, Y) (x).CompareTo((Y)))
result:
a\b\c
a\b\c d
a\b\c d\e
a\b\c\d
a\b\c\d\f
In the result list directories with space is placed before "\".
There are more than 1500000 sub-directories and files
it takes around 50 seconds to sort(default method)
all other methods we tried is taking at least 400 seconds.
how to sort directory path alphabetically?
Is there any built in method to consider Backslash before space ?
You need to break the path up into individual folder names and compare each of them in turn until you find a difference. If there is no difference then you use the length to differentiate, i.e. higher-level folder comes first.
a.Sort(Function(x, y)
Dim xFolderNames As New List(Of String)
Dim yFolderNames As New List(Of String)
'Split first path into folder names.
Do Until String.IsNullOrEmpty(x)
xFolderNames.Insert(0, Path.GetFileName(x))
x = Path.GetDirectoryName(x)
Loop
'Split second path into folder names.
Do Until String.IsNullOrEmpty(y)
yFolderNames.Insert(0, Path.GetFileName(y))
y = Path.GetDirectoryName(y)
Loop
Dim result = 0
'Compare up to as many folders as are in the shortest path.
For i = 0 To Math.Min(xFolderNames.Count, yFolderNames.Count) - 1
result = xFolderNames(i).CompareTo(yFolderNames(i))
If result <> 0 Then
'A difference has been found.
Exit For
End If
Next
If result = 0 Then
'No difference was found so put the shortest path first.
result = xFolderNames.Count.CompareTo(yFolderNames.Count)
End If
Return result
End Function)
For good measure, here's a class that encapsulates that functionality:
Imports System.Collections.ObjectModel
Imports System.IO
Public Class FileSystemPath
Implements IComparable, IComparable(Of FileSystemPath)
Public ReadOnly Property FullPath As String
Public ReadOnly Property PartialPaths As ReadOnlyCollection(Of String)
Public Sub New(fileOrFolderPath As String)
FullPath = fileOrFolderPath
Dim partialPaths As New List(Of String)
Do Until String.IsNullOrEmpty(fileOrFolderPath)
partialPaths.Insert(0, Path.GetFileName(fileOrFolderPath))
fileOrFolderPath = Path.GetDirectoryName(fileOrFolderPath)
Loop
Me.PartialPaths = New ReadOnlyCollection(Of String)(partialPaths)
End Sub
Public Function CompareTo(obj As Object) As Integer Implements IComparable.CompareTo
Return CompareTo(DirectCast(obj, FileSystemPath))
End Function
Public Function CompareTo(other As FileSystemPath) As Integer Implements IComparable(Of FileSystemPath).CompareTo
Dim result = 0
'Compare up to as many folders as are in the shortest path.
For i = 0 To Math.Min(PartialPaths.Count, other.PartialPaths.Count) - 1
result = PartialPaths(i).CompareTo(other.PartialPaths(i))
If result <> 0 Then
'A difference has been found.
Exit For
End If
Next
If result = 0 Then
'No difference was found so put the shortest path first.
result = PartialPaths.Count.CompareTo(other.PartialPaths.Count)
End If
Return result
End Function
Public Overrides Function ToString() As String
Return FullPath
End Function
End Class
It can be used with barely a change to your code:
Dim a As New List(Of FileSystemPath)
a.Add(New FileSystemPath("a\b\c"))
a.Add(New FileSystemPath("a\b\c\d"))
a.Add(New FileSystemPath("a\b\c d"))
a.Add(New FileSystemPath("a\b\c d\e"))
a.Add(New FileSystemPath("a\b\c\d\f"))
a.Sort()
Console.WriteLine(String.Join(Environment.NewLine, a))
Console.ReadLine()

How to create a Boolean Function to list objects in a Generic Collection?

After creating a List(Of T), I want to create aBoolean function. First, we will ask data to add an object in the list. However, in case this new object has the same "DNI" (String attribute from the class Aspirante), then we cannot include this new Object in the list. Therefore, it should be True when we have an Object with the same attribute and False when we don´t, so we can add the new object.
Below is the code I did:
Public Class Oposicion
Private datos As New List(Of Aspirante)()
Public Function Alta(ByRef objAspirante As Aspirante) As Boolean
If datos.Contains(objAspirante.DNI) Then
Return True
Else
datos.Add(objAspirante)
Return False
End If
End Function
End Class
However it doesn´t work. I have no clue on how to do it. Sorry if I was not clear enough.
This doesn't answer your question directly but it involves a significant amount of code, so it won't work in a comment.
You probably shouldn't be using a List(Of T) in the first place. The HashSet(Of T) already includes functionality to prevent adding duplicate items, so that may be a better option. If you want to compare objects on a specific property value then you need to first create a comparer based on that:
Public Class Thing
Public Property Stuff As String
End Class
Public Class ThingComparer
Implements IEqualityComparer(Of Thing)
Public Overloads Function Equals(x As Thing, y As Thing) As Boolean Implements IEqualityComparer(Of Thing).Equals
Return x.Stuff.Equals(y.Stuff)
End Function
Public Overloads Function GetHashCode(obj As Thing) As Integer Implements IEqualityComparer(Of Thing).GetHashCode
Return obj.GetHashCode()
End Function
End Class
You then create a HashSet(Of T) that uses that comparer to determine equality:
Dim things As New HashSet(Of Thing)(New ThingComparer)
You can then just add items as you please by calling Add. That will either add the new item and return True or it will not not add the duplicate item and return False:
Dim variousStuff = {"One", "Two", "One"}
For Each stuff In variousStuff
Dim something As New Thing With {.Stuff = stuff}
If things.Add(something) Then
Console.WriteLine($"'{stuff}' was added successfully.")
Else
Console.WriteLine($"'{stuff}' is a duplicate and was not added.")
End If
Next
The potential drawback is that HasSet(Of T) does not implement IList(Of T), so you cannot access items by index. It does implement ICollection(OF T) though, so it does have a Count property and you can enumerate it with a For Each loop.
You can create a List(Of String) containing the just the DNI property of each object in datos. Then see if the DNI of objAspirante is contained in lst.
Public Class Oposicion
Private datos As New List(Of Aspirante)()
Public Function Alta(objAspirante As Aspirante) As Boolean
Dim lst As New List(Of String)
For Each a As Aspirante In datos
lst.Add(a.DNI)
Next
If lst.Contains(objAspirante.DNI) Then
Return True
Else
datos.Add(objAspirante)
Return False
End If
End Function
End Class
If you can change the type of datos, this might be easier.
Public Class Oposicion
Private datos As New Dictionary(Of String, Aspirante)()
Public Function Alta(objAspirante As Aspirante) As Boolean
If datos.ContainsKey(objAspirante.DNI) Then
Return True
Else
datos.Add(objAspirante.DNI, objAspirante)
Return False
End If
End Function
End Class
If you want to stick with your existing List(Of Aspirante), then simply use .Any() and pass it a Lambda to determine if one already exists. It'd look something like this:
Public Function Alta(ByVal objAspirante As Aspirante) As Boolean
If Not datos.Any(Function(x) x.DNI = objAspirante.DNI) Then
datos.Add(objAspirante)
Return True ' your description and code did not match for these!
End If
Return False ' your description and code did not match for these!
End Function
Note my comment on the Return lines...your code and description did not match here.

VB.Net: Call a module Method or Routine dynamically with parameters

I want to run a Method using a variable name that is stored in a Module with a parameter:
Dim subName as String = "sub1"
Dim param as Integer = 123
sub1(param) <- I want to run this using the **subName** string
I don't want to use Select Case because the Methods are in many different modules and I don't want to maintain a select-case function.
I looked up CallByName but it seems this only works for Classes. I can't figure out how to set the object ObjectRef when it comes to Modules :
Public Function CallByName(ByVal ObjectRef As System.Object,ByVal ProcName As String,ByVal UseCallType As CallType, ByVal Args() As Object) As Object
Is there a way to do this in VB.Net?
Edit: To make it really simple, I need the equivalent of VBA's:
Application.Run module_name.sub_name param
You can use reflection to create a delegate to the methods in the Module. I would load the created delegates into a Dictionary(Of String, Action(Of Int32)).
Action(Of Int32) is chosen because it matches the signature you specified of a subroutine taking an integer parameter.
Assume you have a Module defined like this:
Module SomeModule
Public Sub Sub1(arg As Int32)
Console.WriteLine("Sub1: {0}", arg)
End Sub
Public Sub Sub2(arg As Int32)
Console.WriteLine("Sub2: {0}", arg)
End Sub
End Module
Now to create and store the delegates in a dictionary.
Private methods As New Dictionary(Of String, Action(Of Int32))
Sub LoadMethods()
Dim modType As Type = GetType(SomeModule)
Dim mi As Reflection.MethodInfo
mi = modType.GetMethod("Sub1", BindingFlags.Static Or BindingFlags.Public)
methods.Add(mi.Name, CType(mi.CreateDelegate(GetType(Action(Of Int32))), Action(Of Int32)))
mi = modType.GetMethod("Sub2", BindingFlags.Static Or BindingFlags.Public)
methods.Add(mi.Name, CType(mi.CreateDelegate(GetType(Action(Of Int32))), Action(Of Int32)))
End Sub
You can retrieve and invoke the delegate like this:
methods("Sub1")(123)
methods("Sub2")(456)
Edit: I sometimes makes things to complicated. The LoadMethods method can be done without reflection like this:
Sub LoadMethods()
methods.Add("Sub1", New Action(Of Int32)(AddressOf SomeModule.Sub1))
methods.Add("Sub2", New Action(Of Int32)(AddressOf SomeModule.Sub1))
End Sub
Edit 2: Based on edit to question and comment below.
Edit: To make it really simple, I need the equivalent of VBA's:
Application.Run module_name.sub_name param
You will need to first extract the Module type from its containing assembly based on the entered name. Then you can retrieve the MethodInfo as shown above. The following example assumes that the module is contained in the executing assembly and has minimal checks implemented. It will require you to provide the module name, method name and an array properly typed method arguments. In a real world scenario, it would probably need to take a string of the arguments and perform some type of dynamic type casting to build up the typedArgs array based on calling MethodInfo.GetParameters.
Private Shared Sub Exec(moduleName As String, methodName As String, typedArgs As Object())
Dim asm As Reflection.Assembly = Assembly.GetExecutingAssembly
Dim modType As Type = asm.GetType(String.Format("{0}.{1}", asm.GetName.Name, moduleName))
If modType IsNot Nothing Then
Dim mi As Reflection.MethodInfo
mi = modType.GetMethod(methodName, BindingFlags.Static Or BindingFlags.Public)
If mi IsNot Nothing Then
mi.Invoke(Nothing, typedArgs)
End If
End If
End Sub
Example usage: Exec("SomeModule", "Sub1", New Object() {123})
Lets say you want to call subroutine (or function) sub1 with parameter 123 with optionally given module name module1
Call example, If module name is not available (function name to invoke should be unique among project):
Dim FunctionName As String = "sub1"
Dim Param As Integer = 123
InvokeModuleFunction(FunctionNameToCall:=FunctionName, FunctionParameters:=Param)
Alternatively, If you know module name:
Dim FunctionName As String = "sub1"
Dim Param As Integer = 123
Dim ModuleName As String = "module1"
InvokeModuleFunction(FunctionNameToCall:=FileType, ModuleName:=ModuleName, FunctionParameters:=Param)
InvokeModuleFunction definition
Private Sub InvokeModuleFunction(FunctionNameToCall As String, FunctionParameters As Object, Optional ModuleName As String = Nothing)
Dim MyReflectionAssembly = Reflection.Assembly.GetExecutingAssembly()
Dim MyFunctionType As Type
If IsNothing(ModuleName) Then
'Gets function without ModuleName. FunctionName should be unique in the assembly/programm.
MyFunctionType = MyReflectionAssembly.DefinedTypes.Where(Function(x) x.DeclaredMethods.Where(Function(y) y.Name = FunctionNameToCall).Count > 0).FirstOrDefault
Else
'Gets function using ModuleName, if available
MyFunctionType = MyReflectionAssembly.DefinedTypes.Where(Function(x) x.Name = ModuleName AndAlso x.DeclaredMethods.Where(Function(y) y.Name = FunctionNameToCall).Count > 0).FirstOrDefault
End If
If Not IsNothing(MyFunctionType) Then MyFunctionType.GetMethod(FunctionNameToCall).Invoke(MyFunctionType, New Object() {FunctionParameters})
End Sub
Alternatively you can use more than one parameter in invoking.
You would need to modify the above function to allow to pass more than one parameter.
The invoke part would look like:
FunctionType.GetMethod(FunctionNameToCall).Invoke(FunctionType, New Object() {Par1, Par2, Par3})

VB: Problems with using variable from another class + what to do with not used interface`s functions

I have a problem with getting variable from another class and cannot understand what to do with interface`s functions which have already existed in another class.
What I have:
Form where clicking on a button I should see reversed string:
(I want to call pooraja.StringReverse which is below)
Private Sub btnPoora1_Click(sender As System.Object, e As System.EventArgs) _
Handles btnPoora1.Click
'Dim text As PrjTekstiPooraja.ITeisendused = New PrjTekstiPooraja.CtekstiPooraja
Dim text As PrjTekstiPooraja.ITeisendused = New PrjTekstiPooraja.CtekstiPooraja
Dim pooraja As PrjTekstiPooraja.ITeisendused = New PrjTekstiPooraja.CAlgrotimilinePooraja
text.strText = txtSisendTekst.Text
txtValjundTekst1.Text = pooraja.stringReverse
text.intStart = 1
text.intEnd = Len(txtSisendTekst.Text)
ascFSymbol.Text = text.ascFirstSymbol
ascLSymbol.Text = text.ascLastSymbol()
End Sub
CtekstiPooraja:
(Thiss class will be used to store data.Under data I mean strPooratavText. Data will be used in CAlgoritmilinePooraja)
Public Class CtekstiPooraja
Implements ITeisendused
Public intStartSymbol As Integer
Public intEndSymbol As Integer
Public strPooratavText As String
Private Property intEnd As Integer Implements ITeisendused.intEnd
Get
Return intEndSymbol
End Get
Set(ByVal value As Integer)
intEndSymbol = value
End Set
End Property
Private Property intStart As Integer Implements ITeisendused.intStart
Get
Return intStartSymbol
End Get
Set(ByVal value As Integer)
intStartSymbol = value
End Set
End Property
Public Function pooraText() As String Implements ITeisendused.pooraText
Return StrReverse(strPooratavText)
End Function
Public Property strText As String Implements ITeisendused.strText
Get
Return strPooratavText
End Get
Set(ByVal value As String)
strPooratavText = value
MsgBox(strPooratavText)
End Set
End Property
Public Sub teisendaText(ByRef strSisendText As String) Implements ITeisendused.teisendaText
strPooratavText = StrReverse(strSisendText)
End Sub
Public Function ascFirstSymbol() As String Implements ITeisendused.ascFirstSymbol
Return Asc(GetChar(strPooratavText, intStartSymbol))
End Function
Public Function ascLastSymbol() As String Implements ITeisendused.ascLastSymbol
Return Asc(GetChar(strPooratavText, intEndSymbol))
End Function
Public Function stringReverse() As String Implements ITeisendused.stringReverse
Return Nothing
End Function
End Class
CAlgrotimilinePooraja:
(This class will be called by form button. There I need to use stringReverse function with data from CtekstiPooraja. The problem is that everywhere is used the same interface and there is some functions and procedures from this interface which isnt necessary. I dont know what value should return these unused functions/procedures. Just using "return Nothing or return 0/ "" is bad idea, may be there is possible somehow referenceto to CTekstiPooraja functions/procedures variables")
Public Class CAlgrotimilinePooraja
Implements ITeisendused
Private x As New PrjTekstiPooraja.CtekstiPooraja
Public Function stringReverse() As String Implements ITeisendused.stringReverse
MsgBox(x.strPooratavText)
Dim i As Integer = 0
Dim j As Integer
Dim characters(j) As Char
Dim newString(j) As Char
characters = x.strPooratavText.ToCharArray()
newString = x.strPooratavText.ToCharArray()
Do While i <= j - 1
newString(i) = characters(j - 1)
newString(j - 1) = characters(i)
i += 1
j -= 1
Loop
Return newString
End Function
Public Function ascFirstSymbol() As String Implements ITeisendused.ascFirstSymbol
Return x.ascFirstSymbol()
End Function
Public Function ascLastSymbol() As String Implements ITeisendused.ascLastSymbol
Return Nothing
End Function
Public Property intEnd As Integer Implements ITeisendused.intEnd
Get
Return x.intEndSymbol
End Get
Set(ByVal value As Integer)
End Set
End Property
Public Property intStart As Integer Implements ITeisendused.intStart
Get
Return x.intStartSymbol
End Get
Set(ByVal value As Integer)
End Set
End Property
Public Function pooraText() As String Implements ITeisendused.pooraText
Return x.pooraText()
End Function
Public Property strText As String Implements ITeisendused.strText
Get
Return x.strPooratavText
End Get
Set(ByVal value As String)
End Set
End Property
Public Sub teisendaText(ByRef strSisendText As String) Implements ITeisendused.teisendaText
x.strPooratavText = StrReverse(strSisendText)
End Sub
End Class
MyInterface:
Public Interface ITeisendused
Property intStart As Integer
Property intEnd As Integer
Property strText As String
Function pooraText() As String
Function ascFirstSymbol() As String
Function ascLastSymbol() As String
Function stringReverse() As String
Sub teisendaText(ByRef strSisendText As String)
End Interface
I cannot understand how to get variable strPooratavText from CTekstiPooraja to CAlgrotimilinePooraja. Usually that instancewhich I create worked but not now. And I cannot understand what to do with already existed function and procedures in CAlgoritmilinePooraja when the same function and procedures has in another class. Maybe, it is possible to reference them somehow to existed functions/procedures in CTekstiPooraja? Could you explain me how to id, already tired to surf Internet to find a solution for it, have already try a lot.
Well, I think you have a fundamental problem with understanding interfaces. They describe data and behavior, it should be extremely rare to want to implement part of an interface.
That said, if you do want to implement part of an interface, instead of returning bogus data, throw an exception for behavior you don't implement.
Your specific problem is that CAlgoritmilinePooraja works on an instance of CtekstiPooraja, but it creates a new instance instead of using an existing one. Add
Sub New(incomingX as CtekstiPooraja)
x = incomingX
End Sub
to CAlgoritmilinePooraja. And then in your event, use....
Dim text As PrjTekstiPooraja.CtekstiPooraja = New PrjTekstiPooraja.CtekstiPooraja
text.strText = txtSisendTekst.Text
Dim pooraja As PrjTekstiPooraja.ITeisendused = New PrjTekstiPooraja.CAlgrotimilinePooraja(text)
That is the minimum change to your design that gets what you want to happen to happen but it's problably not what you should do. Other than implementing strReverse, CtekstiPooraja seems to be what you want, CAlgrotimilinePooraja looks to do just one thing, the actual string reversal.
I would move the implementation of strReverse into CtekstiPooraja, and then eliminate CAlgrotimilinePooraja.
PS I would try to stick to English for class names as well as functions and variables.

VB.Net List.Find. Pass values to predicate

Having a bit of trouble using the List.Find with a custom predicate
i have a function that does this
private function test ()
Dim test As Integer = keys.Find(AddressOf FindByOldKeyAndName).NewKey
here's the function for the predicate
Private Shared Function FindByOldKeyAndName(ByVal k As KeyObj) As Boolean
If k.OldKey = currentKey.OldKey And k.KeyName = currentKey.KeyName Then
Return True
Else
Return False
End If
End Function
by doing it this way means i have to have a shared "currentKey" object in the class, and i know there has to be a way to pass in the values i'm interested in of CurrentKey (namely, keyname, and oldkey)
ideally i'd like to call it by something like
keys.Find(AddressOf FindByOldKeyAndName(Name,OldVal))
however when i do this i get compiler errors.
How do i call this method and pass in the values?
You can cleanly solve this with a lambda expression, available in VS2008 and up. A silly example:
Sub Main()
Dim lst As New List(Of Integer)
lst.Add(1)
lst.Add(2)
Dim toFind = 2
Dim found = lst.Find(Function(value As Integer) value = toFind)
Console.WriteLine(found)
Console.ReadLine()
End Sub
For earlier versions you'll have to make "currentKey" a private field of your class. Check my code in this thread for a cleaner solution.
I have an object that manages a list of Unique Property Types.
Example:
obj.AddProperty(new PropertyClass(PropertyTypeEnum.Location,value))
obj.AddProperty(new PropertyClass(PropertyTypeEnum.CallingCard,value))
obj.AddProperty(new PropertyClass(PropertyTypeEnum.CallingCard,value))
//throws exception because property of type CallingCard already exists
Here is some code to check if properties already exist
Public Sub AddProperty(ByVal prop As PropertyClass)
If Properties.Count < 50 Then
'Lets verify this property does not exist
Dim existingProperty As PropertyClass = _
Properties.Find(Function(value As PropertyClass)
Return value.PropertyType = prop.PropertyType
End Function)
'if it does not exist, add it otherwise throw exception
If existingProperty Is Nothing Then
Properties.Add(prop)
Else
Throw New DuplicatePropertyException("Duplicate Property: " + _
prop.PropertyType.ToString())
End If
End If
End Sub
I haven't needed to try this in newer versions of VB.Net which might have a nicer way, but in older versions the only way that I know of would be to have a shared member in your class to set with the value before the call.
There's various samples on the net of people creating small utility classes to wrap this up to make it a little nicer.
I've found a blog with a better "real world" context example, with good variable names.
The key bit of code to Find the object in the list is this:
' Instantiate a List(Of Invoice).
Dim invoiceList As New List(Of Invoice)
' Add some invoices to List(Of Invoice).
invoiceList.Add(New Invoice(1, DateTime.Now, 22))
invoiceList.Add(New Invoice(2, DateTime.Now.AddDays(10), 24))
invoiceList.Add(New Invoice(3, DateTime.Now.AddDays(30), 22))
invoiceList.Add(New Invoice(4, DateTime.Now.AddDays(60), 36))
' Use a Predicate(Of T) to find an invoice by its invoice number.
Dim invoiceNumber As Integer = 1
Dim foundInvoice = invoiceList.Find(Function(invoice) invoice.InvoiceNumber = invoiceNumber)
For more examples, including a date search, refer to Mike McIntyre's Blog Post