Introduction
There're quite a few ways to find an object inside a Generic List
, but we'll focus on one specific method List<T>.Find()
, and investigate its behavior when working with value types and reference types.
Background
While working with Generics the other day, I stumbled on a few things that are important to keep in mind when using the Find()
method on a generic list. Especially when working with value types, and your search criteria is literally the default value returned when no results were found. I ended up discovering a few things about Find()
and how to use it on your own custom classes. There's also a simple helper method that takes in an array of custom types and checks if the specified object was found.
I hope this helps someone understand a bit better on what's going on when using the Find()
method on a Generic List
.
More about List<T>.Find()
List<T>.Find Method
- Searches for an element that matches the conditions defined by the specified predicate, and returns the first occurrence within the entire List<T>.
A simple example is a generic list consisting of integers:
List<int> intList1 = new List<int>(new int[] {1,2,3,4});
int intResult = intList1.Find
(
delegate(int intpar1){return intpar1 == 3}
);
which would basically return the integer if it was found in the list.
However, consider the response when the integer wasn't found:
List<T>.Find
would return the default value depending on the type:
Extract from MSDN help:
When searching a list containing value types, make sure the default value for the type does not satisfy the search predicate. Otherwise, there is no way to distinguish between a default value indicating that no match was found and a list element that happens to have the default value for the type. If the default value satisfies the search predicate, use the FindIndex
method instead.
So, be careful when using .Find()
when your search criteria is literally the default value for the type. (This is especially relevant when using value types.)
One way of handling this is to turn your value types into nullable types. That way, you can avoid 0 being returned when your integer wasn't found in the list:
List<Nullable<int>> intListTemp2 = new List<int?>
(
new int?[] { 1, 2, 3, 4 }
);
int? intFoundAnon = intListTemp2.Find
(
delegate(int? intInput1){return intInput1 == 5;}
);
Instead of 0, null is returned if the value wasn't found in the list of value types.
This can be very useful when working with database null values being used in columns that have types such as int
or DateTime
.
There's another thing to keep in mind when searching for a reference type inside a list:
Reference equality vs. value equality
Value equality is the generally understood meaning of equality: it means that two objects contain the same values. For example, two integers with the value of 2 have value equality.
Reference equality means that there are not two objects to compare. Instead, there are two object references, and both of them refer to the same object.
When you are doing a .Find()
on a value type (such as int
, bool
, char
, DateTime
, enum
, Decimal
, ..), the elements are compared based on the value itself. But, if you do a .Find()
on a reference type (like a custom class), you'd be looking for objects that have the same reference.
To explain it more: even if you compare two objects with the same values for all properties, it won't necessarily return true
.
Patient patTemp1 = new Patient("John");
Patient patTemp2 = new Patient("John");
bool blResult = patTemp1.Equals(patTemp2);
Instead, you should make sure your custom class overrides the Equals()
method and define how the two objects should be compared.
class Patient : IComparable
public override bool Equals(object obj)
{
return this.Firstname == ((Patient) obj).Firstname;
}
And, remember to make sure your custom class inherits from the interface "IComparable
" as well.
If you remember to do that, you can do a Find()
on your Generic List
and it will play along as expected (won't return false
every time).
List<Patient> PatientList = new List<Patient>
(
new Patient[]
{
new Patient("John Smith"), new Patient("Victor Matfield")
}
);
bool blresult = PatientList.Exists
(
delegate(Patient pTempSearch)
{
return pTempSearch.Equals(new Patient("John Smith"));
}
);
The following helper method takes in an array of custom types and checks if the specified object was found.
public bool ArrayContains<T>(T[] array, T value)
{
return array != null &&
Array.Exists(array, delegate(T tTemp)
{
return tTemp.Equals(value);
});
}
bool blResult = ArrayContains<Patient>(PatientArray, Patient1);
Points of Interest
The code example included covers everything in a bit more depth. If you're like me, looking at the actual code will give some additional insight.
Also, note the use of Nullable Types when using Find()
on value types such as Int32
.
History
This is the first post and release.