Click here to Skip to main content
Click here to Skip to main content

Using Find in a Generic List

By , 27 Oct 2008
 

GenericsFind

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:

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"); 

// this will return false
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

johan_vw
Software Developer (Senior)
South Africa South Africa
Member
I'm a senior software developer from South Africa and have a huge passion for .NET and Microsoft related technologies.

When I'm not having fun figuring out an architecture or class design, you'd probably find me rocking on the drums, running around with my badminton racquet or enjoying another bitter coffee or an ice-cold Amstel at the bar.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionConversion to VB.NETmemberwanderces12 Nov '08 - 9:00 
how convert this code to VB.NET that delegate not go!
GeneralThe IEquatable Interface versus bool Equals(obj)memberMartin.Holzherr30 Oct '08 - 0:50 
Failing to implement the IEquatable<T> interface when using container functions like Contains which use this interface does not result in a logic error provided the user implemented the overridable method bool Equals(object obj).
Failing to implement bool Equals(object obj) appropriately in your class/struct on the other hand is a serious error even when you implement the IEquatable<T>
interface
So why should you then implement the IEquatable<T> interface at all. There is one advantage: runtime performance can be much better when the IEquatable<T> is used in an algorithm.
To test that, I created several classes/struct which had a single data field named id and which only differed in the support of the IEquatable<T> interface. For each of the classes/structs I created a list with 1'000'000 objects and called the member function Contains. The difference in runtime performance for classes/structs supporting the IEquatable<T> interface versuses classes/structs not supporting it was a factor of 5 to 8.
So it can make a difference. It is therefore not surprising that all the built in types like 'int' , 'string' and so on implement the IEquatable<T> interface.
GeneralRe: The IEquatable Interface versus bool Equals(obj)memberjohan_vw30 Oct '08 - 3:43 
Hi Martin,
 
Thanks for the feedback and posts - it sounds like you know your way around Generics and enjoy them as well. You mentioned quite a few very interesting points, and I wasn't aware of the performance boost when including support for IEquatable inside a custom class/struct. Thank you for pointing that one out.
 
The article tried to illustrate the necessity of overriding the 'Equals' method when trying to compare two reference objects. To override the Equals method (which is in turn used when calling Find()) you only need to inherit from IComparable.
 
It's true, my implementation of the 'Equals' method didn't cater for necessary checks on the obj parameter and one would definately be better of making use of the check you suggested.
 
The article also mentions another usefull link "overrides the Equals() method" that also have a few guidelines for overriding Equals(). Another step to improve the code would've been to introduce exception handling as well.
 
You've pointed out two very important points, thanks again for the feedback.

Generaloverride of bool Equals(object obj) does not adhere to standard implementation practicememberMartin.Holzherr30 Oct '08 - 0:12 
Your override of the Equals(Object obj) method in the Person class
does not ahere to the standard implementation practice as described
in http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx[^].
Standard practice conforming implemenation would be
public override bool Equals(object obj)
{
   if (obj == null || GetType() != obj.GetType()) return false;
   return this.Firstname == ((Person)obj).Firstname;        
}
but your implementation is
public override bool Equals(object obj)
{
   return this.Firstname == ((Person)obj).Firstname;        
}
The additional test obj == null || GetType() != obj.GetType())
ensures that your program does not crash when Equals is called with a null reference and that it correctly handles the case where Equals is called with a derivation or a base of the class which implements Equals
GeneralRe: override of bool Equals(object obj) does not adhere to standard implementation practicememberTobiasP15 Nov '08 - 4:37 
Another thing to keep in mind, also mentioned in the previous link, is that two objects that are equal must return the same hash code, or they will not work correctly in hash tables. Therefore, classes that override Equals should also override GetHashCode. See MSDN: Object.GetHashCode Method[^].
GeneralC# 3.0+ Lambda FunctionsmemberQistoph29 Oct '08 - 0:45 
Since C# 3.0 it is possible to use Lambda functions, which (imho) improve readability of your code.
 
Take for example this code:
int intResult = intList1.Find
(
    delegate(int intpar1){return intpar1 == 3}
);
 
Using Lambda functions it would be:
int intResult = intList1.Find(
    intpar1 => intpar1 == 3
);
 
More about Lambda function can be found online. (Lambda Expressions on MSDN)
GeneralRe: C# 3.0+ Lambda Functionsmemberjohan_vw29 Oct '08 - 1:22 
Hi Qistoph,
 
thanks for the comment - yes, your right.. it does improve the readability quite a lot .. good catch Smile | :)
 
johan_vw
GeneralIEquatable Interface should be supported in your custom classmemberMartin.Holzherr28 Oct '08 - 4:23 
This article uses mainly generic types but only mentions the
old (pre-generic) method bool Equals(object obj);.
A type used with generic functions and containers should
additionally support the IEquatable interface which
features a typed Equals method bool Equals(T t);.
The IComparable interface mentioned in the article is not used
in functions like Find,Contains,FindIndex
(but it's true, a type used with generic containers and functions
should support it and should als support the typed version IComparable<T>).

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 28 Oct 2008
Article Copyright 2008 by johan_vw
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid