Fun bit of code here using System.Reflection!
I have a class that contains an array of objects. I wanted a convenient way to be able to access elements by the value of a property within the object, for example, if I have an array of type Employee and I want to find the one in the array where the LastName Property is Smith, it would be nice to be able to call MyArray["EmployeeID", "12345"] to get the item. Basically, adding some Dictionary-type lookup options to an indexed array is what I wanted.
My solution is:
public class ccnGenericCollection<T>
{
#region Fields
private T[] _Items;
#endregion
#region Properties
public T[] Items
{
get { return _Items; }
set { _Items = value; }
}
public T this[int index]
{
get { return _Items[index]; }
set { _Items[index] = value; }
}
public T this[string fieldName, object searchValue]
{
get
{
// Make sure the items and key field name are initialized
if (null != _Items)
{
// Get the member
MemberInfo[] MyMembers = typeof(T).GetMember(fieldName);
if ((null != MyMembers) && (MyMembers.GetLength(0) > 0))
{
MemberInfo MyMember = MyMembers[0];
// Loop through the items
for (int i = 0; i < _Items.GetLength(0); i++)
{
object MyKeyValue;
// Get the field or property value
switch (MyMember.MemberType)
{
case MemberTypes.Field:
MyKeyValue = ((FieldInfo)MyMember).GetValue(this[i]);
break;
case MemberTypes.Property:
MyKeyValue = ((PropertyInfo)MyMember).GetValue(this[i], null);
break;
default:
throw new Exception(fieldName + " must be a field or a property");
}
if (MyKeyValue.Equals(searchValue))
{
// Found our item
return _Items[i];
}
}
}
}
// Not found, return a default item
return default(T);
}
set
{
if (null != _Items)
{
// Get the member
MemberInfo[] MyMembers = typeof(T).GetMember(fieldName);
if ((null != MyMembers) && (MyMembers.GetLength(0) > 0))
{
MemberInfo MyMember = MyMembers[0];
// Loop through the items
for (int i = 0; i < _Items.GetLength(0); i++)
{
switch (MyMember.MemberType)
{
case MemberTypes.Field:
if (((FieldInfo)MyMember).GetValue(this[i]).ToString() == searchValue)
{
// Set the item
_Items[i] = value;
}
break;
case MemberTypes.Property:
if (((PropertyInfo)MyMember).GetValue(this[i], null).ToString() == searchValue)
{
// Set the item
_Items[i] = value;
}
break;
}
}
}
}
// No item found, throw a null reference exception
throw new NullReferenceException();
}
}
#endregion
#region Operators
public static implicit operator T[](ccnGenericCollection<T> input)
{
// Automatically convert from an array of the underlying class to the generic collection
T[] Output = (T[])input.Items.Clone();
return Output;
}
public static implicit operator ccnGenericCollection<T>(T[] input)
{
// Automatically convert from the generic collection to an array of the underlying class
ccnGenericCollection<T> Output = new ccnGenericCollection<T>();
Output.Items = (T[])input.Clone();
return Output;
}
public static implicit operator List<T>(ccnGenericCollection<T> input)
{
// Automatically convert from an array of the underlying class to the generic collection
List<T> Output = new List<T>();
if (null != input.Items)
{
Output.AddRange(input.Items);
}
return Output;
}
public static implicit operator ccnGenericCollection<T>(List<T> input)
{
// Automatically convert from the generic collection to an array of the underlying class
ccnGenericCollection<T> Output = new ccnGenericCollection<T>();
Output.Items = (T[])input.ToArray().Clone();
return Output;
}
#endregion
#region Methods
public int Add(T newItem)
{
// Get the list of the current items
List<T> MyList = (List<T>)this;
// Add the new item
MyList.Add(newItem);
// Set the items to the array of the list
_Items = MyList.ToArray();
// Return the highest index of the items
return _Items.GetUpperBound(0);
}
#endregion
}
This solution allows the field name for the search to be either a field or a property.
Note that it’s prudent to make sure that the field used to access the class is unique; it’ll return the first match for get and update all matches for set.
I got a surprise the first time I ran it when I never got any matches back by using MyKeyField == searchValue as the condition; I had to use MyKeyField.Equals(searchValue) to use the object equality. This also means that it’s easy to define what equality is for a given class by implementing the == operator for the class.
The implicit operators come in handy as well; basically any class that implements this class can be converted to/from arrays and lists.
