chris carter's web log

Home |  Contact |  Admin
 

Pluck Me!

Posted on September 27, 2007

Last September I wrote something inspired by the pluck method prototype javascript library, Pluck<T> and posted it on CodeKeep. Basically it creates an array of some type and spins through a collection and picks off the values of the specified property and puts them in an array.  Let's assume we have this class:

public class Customer{
public int Age;
public string Name;
public DateTime Birthday;
Customer(int age, string name, DateTime birthday){
this.Age = age;
this.Name = name;
this.Birthday = birthday;
}
}

And now we create a collection of these guys like this:

List<Customer> customers = new List<Customer>();
customers.Add(new Customer(37, new DateTime(1970, 6, 18), "Chris"));
customers.Add(new Customer(36, new DateTime(1971, 3, 30), "Anja"));
customers.Add(new Customer(3, new DateTime(2004, 4, 11), "Riley"));
customers.Add(new Customer(1, new DateTime(2006, 10, 14), "Emmitt"));

Now I want an array of ages for all of the customers, pre-Pluck you might do something like this:

int[] ages = new int[customers.Count];
for(int i=0;i<customers.Count;i++){
ages[i] = customers[i].Age;
}

...or something similar. What I wanted though was a simple one liner, so I wrote Pluck. Funnily enough, if you didn't look at the CodeKeep sample, here's the first version of Pluck:

public class PluckableList<T> : List<T>{
public Array Pluck(string propertyName){
Type type = typeof(T);
PropertyInfo property = type.GetProperty(propertyName);
Array result = Array.CreateInstance(property.PropertyType, this.Count);
for(int i=0;i<this.Count;i++){
result.SetValue(type.InvokeMember(propertyName, BindingFlags.GetProperty, null, this[i], null), i);
}
return result;
}
}

Well that was nice but I wanted to push this into a static helper class, so without looking at this example(because I thought I remembered the code) I wrote this this morning:

public static T[] Pluck<T, U>(IList<U> items, string propertyName){
T[] result = null;
if (items == null || String.IsNullOrEmpty(propertyName))
return result;

Type type = items[0].GetType();
PropertyInfo property = type.GetProperty(propertyName);
if (!property.CanRead || !typeof(T).IsAssignableFrom(property.PropertyType))
return result;

result = new T[items.Count];
for (int i = 0; i < items.Count; i++){
result[i] = (T)type.InvokeMember(propertyName, BindingFlags.GetProperty, null, items[i], null);
}
return result;
}

The reflective call was bugging me. It wouldn't catch compile time errors. How do I get compile time checking on the pluck method?

Bring in Generic Delegates

I got inspired by the generic Array.Find method that has this signature:

Array.Find(T[] array, Predicate match)

So I came up with my first generic delegate which has this signature:

public delegate TOut Inspector<TOut, TItem>(TItem item);

Next was to create an overload for the Pluck method that would accept that delegate signature as the second parameter. The new method looks like this:

public static TOut[] Pluck<TOut, TItem>(IList<TItem> items, Inspector<TOut, TItem> inspector){
TOut[] result = new TOut[items.Count];
for (int i = 0; i < items.Count; i++){
result[i] = inspector(items[i]);
}
return result;
}

So now if I want the ages of my customers,

int[] ages = ArrayUtils.Pluck<int, Customer>(_customers, delegate(Customer c) { return c.Age; });

VOILA! strongly typed Pluck.  I'm sure something like this already exists somewhere in the framework or is coming as part of 3.x but it was fun writing.

*** UPDATE *** It was pointed out, by Atif, that Array.ConvertAll<TInput,TOutput> will do the trick. This is true so long as you already have an array, otherwise you'll need to create one before using the method. Using Array.ConvertAll with this example and using the fact that List<T> has a ToArray() method, it would look like this:

int[] ages = Array.ConvertAll<Customer, int>(_customers.ToArray(), delegate(Customer c) { return c.Age; });

But while poking around the System.dll using reflector I noticed the Converter generic delegate which is the exact same think as my Inspector delegate, so I switched to using the Converter, the method internals for Pluck did not change at all. So now Pluck looks like this:

public static TOut[] Pluck<TOut, TItem>(IList<TItem> items, Converter<TItem, TOut> converter){
if (items == null || items.Count == 0 || converter == null)
return null;

TOut[] result = new TOut[items.Count];
for (int i = 0; i < items.Count; i++){
result[i] = converter(items[i]);
}
return result;
}

kick it on DotNetKicks.com

Comments

Atif Aziz

Yes, something like this already exists in the .NET Framework 2.0. It's called Array.ConvertAll<TInput,TOutput>.

Chris Carter

Crap! I even use that one! I'm an idiot, oh well, I still like the name Pluck better :) Thanks

Chris Carter

Wait, although I can get that to work, Array.ConvertAll still expects an array which I don't have, meaning the array needs to be created. So Pluck still might have life in it yet.

Post a Comment

(required)
(required)
(no HTML!)