Many times,
even in .NET v2 with its lovely List<T>, you need to work with arrays.
Many of those many times you will want to perform some common tasks such as
checking if an element exists in the array or not. Unfortunately arrays do not
have a Contains() method so you end up writing the same few lines of code over
and over again until you decide you’ve had enough and you add a static method
for it in your library.
In this
particular case, with version 1 of the framework you would have two options.
Either use the IEnumerable interface and loop through the array using a foreach
statement or loop through the array using an index.
IEnumerable
public static bool Contains(IEnumerable array, object
value)
{
foreach
(object obj in
array)
{
if
(obj.Equals(value))
{
return
true;
}
}
return false;
}
Index
public static bool Contains(Array array, object
value)
{
for (int index = 0; index < array.Length; index++)
{
if
(array.GetValue(index).Equals(value))
{
return
true;
}
}
return false;
}
Both of these
methods will work fine and continue to work fine in version 2 of the framework.
As you have probably noticed though, there is no differentiation made between
arrays that contain reference types and those that contain value types. In
other words, performance is not optimal for when arrays containing value types
are used. Here are a few numbers from when using these two ways of iterating
over large arrays (average out of 3 times):
IEnumerable
Array of
integers: 2921ms
Array of
strings: 937ms
Array of
objects: 863ms
Index
Array of
integers: 2750ms
Array of
strings: 816ms
Array of
objects: 762ms
As expected
using the index rather than the IEnumerable interface is quicker, but in both
cases there is a performance hit when iterating through an array of integers.
We can
overcome this issue with the help of Generics. Here is the code:
public class ArrayHelper<T>
{
private
ArrayHelper()
{
}
public static bool Contains(T[] array, T value)
{
for
(int index = 0; index < array.Length;
index++)
{
if
(Singleton.Comparer.Equals(array[index],
value))
{
return
true;
}
}
return
false;
}
private
class Singleton
{
public
static readonly
EqualityComparer<T> Comparer = EqualityComparer<T>.Default;
}
}
The Contains
method takes an array of type T, which we still don’t know if it is a reference
or a value type. Since it performs better we use the index to go through the
array, just like before, only now the comparison is handled by an instance of
EqualityComparer<T>. What the Default property does is explained nicely
by the documentation
The
Default property checks whether type T implements the System.IEquatable generic
interface and if so returns an EqualityComparer that uses that implementation.
Otherwise it returns an EqualityComparer that uses the overrides of
Object.Equals and Object.GetHashCode provided by T.
This allows
for the comparison of value types without boxing them. Let’s have a look at how
this solution performs running the same test:
Array of
integers: 97ms
Array of
strings: 388ms
Array of
objects: 327ms
Ahhh, that’s better. The
reason for the class Singleton is simply thread-safe lazy initialization and is
not really required if the class ArrayHelper is only going to have the Contains
method, but I am sure a few more methods will make their way in there in the
future.