For a rectangular VB.NET array such as T(,), the dependable generic search pattern is a pair of nested loops. A function that accepts Predicate(Of T) can search integers, strings, dates, structures, classes, or custom conditions while returning the first match and its row and column.
Array.Find(Of T) is not a direct substitute: its documented generic overload accepts a one-dimensional T() array and returns a matching value, not two-dimensional coordinates. See Microsoft’s Array.Find documentation.
As an Amazon Associate I earn from qualifying purchases.
The production-ready generic function
The result should include an explicit Found flag. Returning only T is ambiguous because 0, False, Nothing, or a default structure may be a legitimate array value.
Imports System
Public NotInheritable Class SearchHit(Of T)
Public ReadOnly Property Found As Boolean
Public ReadOnly Property Row As Integer
Public ReadOnly Property Column As Integer
Public ReadOnly Property Value As T
Private Sub New(found As Boolean,
row As Integer,
column As Integer,
value As T)
Me.Found = found
Me.Row = row
Me.Column = column
Me.Value = value
End Sub
Public Shared Function Match(row As Integer,
column As Integer,
value As T) As SearchHit(Of T)
Return New SearchHit(Of T)(True, row, column, value)
End Function
Public Shared Function NotFound() As SearchHit(Of T)
Return New SearchHit(Of T)(False, -1, -1, Nothing)
End Function
End Class
Public Module ArraySearch
Public Function Find2D(Of T)(
array As T(,),
match As Predicate(Of T)
) As SearchHit(Of T)
If array Is Nothing Then
Throw New ArgumentNullException(NameOf(array))
End If
If match Is Nothing Then
Throw New ArgumentNullException(NameOf(match))
End If
Dim firstRow As Integer = array.GetLowerBound(0)
Dim lastRow As Integer = array.GetUpperBound(0)
Dim firstColumn As Integer = array.GetLowerBound(1)
Dim lastColumn As Integer = array.GetUpperBound(1)
For row As Integer = firstRow To lastRow
For column As Integer = firstColumn To lastColumn
Dim value As T = array(row, column)
If match(value) Then
Return SearchHit(Of T).Match(row, column, value)
End If
Next
Next
Return SearchHit(Of T).NotFound()
End Function
End Module
The predicate determines what counts as a match. The function scans the first dimension, then the second, and stops at the first matching cell.
#1 Best Overall
Using the function
Exact integer search
Dim numbers(,) As Integer = {
{10, 20, 30},
{40, 50, 60},
{70, 80, 90}
}
Dim result = Find2D(numbers, Function(value) value = 50)
If result.Found Then
Console.WriteLine($"Found {result.Value} at row {result.Row}, column {result.Column}")
Else
Console.WriteLine("Value was not found.")
End If
This prints:
Found 50 at row 1, column 1
Case-insensitive string search
Dim names(,) As String = {
{"Alice", "Bob"},
{"Carol", "Diana"}
}
Dim result = Find2D(
names,
Function(value) String.Equals(
value,
"diana",
StringComparison.OrdinalIgnoreCase))
If result.Found Then
Console.WriteLine($"Found at ({result.Row}, {result.Column})")
End If
The predicate also supports ranges, compound conditions, and property checks:
Dim result = Find2D(numbers, Function(value) value >= 40 AndAlso value <= 60)
Why Array.Find does not solve this directly
This valid call uses a one-dimensional array:
Dim values() As Integer = {10, 20, 30}
Dim value = Array.Find(
values,
Function(number) number > 15)
The documented signature is based on T(), not rectangular T(,). It returns the first matching element and does not preserve row and column information. Array.FindIndex has the same one-dimensional focus, returning a linear index rather than a two-dimensional coordinate. A nested-loop function is therefore the clearest general solution for a rectangular array.
This does not mean no API can ever work with a base Array; APIs such as Array.GetValue can retrieve values through multidimensional indexes. However, using Array as the main parameter loses compile-time guarantees about the element type, rank, and rectangular shape. Prefer T(,) for this specific function.
Array declaration, dimensions, and traversal order
A rectangular two-dimensional array uses a comma in its type declaration:
Dim values(,) As Integer
Dim values(2, 3) As Integer
For a normal zero-based declaration, values(2, 3) has three elements in the first dimension and four in the second. Access uses comma-separated indexes:
Rank #2
Dim item As Integer = values(row, column)
values(row, column) = 123
The recommended implementation searches in the order imposed by its loops:
(0,0), (0,1), (0,2), ...
(1,0), (1,1), (1,2), ...
For duplicate values, “first” means the first coordinate encountered in that order. This is a property of the function’s traversal, not a separate multidimensional search order supplied by Array.Find.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Most ordinary VB.NET arrays are zero-based, so this loop is also common:
For row As Integer = 0 To array.GetLength(0) - 1
For reusable code, GetLowerBound and GetUpperBound are safer. GetLength(dimension) returns the number of elements in a dimension; the bound methods return its actual lowest and highest indexes. This also supports arrays created with nonzero lower bounds through .NET array APIs. Microsoft’s Visual Basic array documentation covers these dimension methods and indexing rules.
A convenient equality overload
Callers performing exact equality searches should not need to write a predicate every time:
Imports System.Collections.Generic
Public Function Find2D(Of T)(
array As T(,),
value As T,
Optional comparer As IEqualityComparer(Of T) = Nothing
) As SearchHit(Of T)
If array Is Nothing Then
Throw New ArgumentNullException(NameOf(array))
End If
If comparer Is Nothing Then
comparer = EqualityComparer(Of T).Default
End If
Return Find2D(
array,
Function(current) comparer.Equals(current, value))
End Function
Usage is concise:
Dim result = Find2D(numbers, 50)
Use the predicate overload when you need case-insensitive text, ranges, projections, culture-specific rules, or compound criteria. The equality overload uses EqualityComparer(Of T).Default; it does not automatically mean case-insensitive string comparison.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Returning every matching coordinate
If duplicate values matter, collect all hits instead of returning immediately:
Imports System.Collections.Generic
Public Function FindAll2D(Of T)(
array As T(,),
match As Predicate(Of T)
) As List(Of SearchHit(Of T))
If array Is Nothing Then
Throw New ArgumentNullException(NameOf(array))
End If
If match Is Nothing Then
Throw New ArgumentNullException(NameOf(match))
End If
Dim results As New List(Of SearchHit(Of T))()
For row As Integer = array.GetLowerBound(0) To array.GetUpperBound(0)
For column As Integer = array.GetLowerBound(1) To array.GetUpperBound(1)
Dim value As T = array(row, column)
If match(value) Then
results.Add(SearchHit(Of T).Match(row, column, value))
End If
Next
Next
Return results
End Function
Dim matches = FindAll2D(
numbers,
Function(value) value Mod 20 = 0)
The scan still takes O(rows × columns) time, while the returned collection uses O(k) additional space for k matches.
When only a Boolean is needed
A Boolean-only search avoids creating a result object:
Public Function Contains2D(Of T)(
array As T(,),
match As Predicate(Of T)
) As Boolean
If array Is Nothing Then
Throw New ArgumentNullException(NameOf(array))
End If
If match Is Nothing Then
Throw New ArgumentNullException(NameOf(match))
End If
For row As Integer = array.GetLowerBound(0) To array.GetUpperBound(0)
For column As Integer = array.GetLowerBound(1) To array.GetUpperBound(1)
If match(array(row, column)) Then
Return True
End If
Next
Next
Return False
End Function
An empty array naturally returns False. A null array or null predicate is rejected with ArgumentNullException, matching the conventional behavior documented for Array.Find.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rectangular arrays are not jagged arrays
These declarations describe different types:
Dim rectangular(,) As Integer
Dim jagged()() As Integer
A rectangular array has one fixed two-dimensional shape. A jagged array is an array whose elements are arrays, so rows may have different lengths. The T(,) function cannot accept T()().
For a jagged array, use row-specific lengths and indexing such as array(row)(column):
Public Function FindJagged(Of T)(
array As T()(),
match As Predicate(Of T)
) As SearchHit(Of T)
If array Is Nothing Then
Throw New ArgumentNullException(NameOf(array))
End If
If match Is Nothing Then
Throw New ArgumentNullException(NameOf(match))
End If
For row As Integer = 0 To array.Length - 1
If array(row) Is Nothing Then Continue For
For column As Integer = 0 To array(row).Length - 1
Dim value As T = array(row)(column)
If match(value) Then
Return SearchHit(Of T).Match(row, column, value)
End If
Next
Next
Return SearchHit(Of T).NotFound()
End Function
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Performance and alternatives
For R rows and C columns, a first-match search has:
- Worst-case time: O(R × C).
- Best-case time: O(1) when the first cell matches.
- Extra space: O(1).
- All-match space: O(k), where
kis the number of matches.
This is a linear scan over the array’s total cells. A generic parameter provides type safety and reuse; it does not automatically make scanning faster.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsIf only a Boolean is needed, a LINQ query can flatten the array:
Best Value
Dim found As Boolean =
numbers.Cast(Of Integer)().
Any(Function(value) value = 50)
This is reasonable when coordinates are irrelevant. Once row and column matter, nested loops are more transparent and avoid having to reconstruct coordinates from a flattened sequence.
Array.BinarySearch is not a general replacement. Binary search requires a one-dimensional array sorted according to compatible comparison rules; a rectangular matrix is not automatically arranged for that algorithm. See Microsoft’s Array.BinarySearch documentation.
For frequent exact lookups, build an index such as Dictionary(Of T, List(Of Coordinate)) rather than scanning the entire array for every request. If the data represents records, a List(Of T) where T is a class or structure may be a better model. For persistent or query-heavy data, a database index is usually more appropriate.
Important edge cases
- Missing value: check
result.Found; do not infer failure fromValue. Nothingvalues: a reference-type array can legitimately containNothing, so search withFunction(value) value Is Nothing.- Duplicates:
Find2Dreturns the first hit; useFindAll2Dfor every hit. - Empty arrays: return not found without throwing.
- Nonzero bounds: use the actual lower and upper bounds rather than hard-coding zero.
- Custom objects: predicates can inspect properties, for example
Function(person) person.Id = 42.
Tests worth writing
A reusable search helper should be checked against:
Quick Recap
- a match in the first cell;
- a match in the last cell;
- no match;
- duplicate values;
- an empty array;
- case-sensitive and case-insensitive string rules;
- a legitimate
Nothingvalue; - custom predicates and custom equality comparers;
- nonzero lower-bound arrays if that scenario is supported.
Decision guide
| Need | Use |
|---|---|
| One lookup in a rectangular array | Find2D(Of T) with nested loops |
| Exact equality with a simple call | The equality overload |
| Only existence | Contains2D(Of T) |
| Every duplicate match | FindAll2D(Of T) |
| Rows with different lengths | A separate jagged-array function |
| Many repeated exact lookups | A dictionary index |
| Query-heavy structured data | Records, collections, or a database |
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




