Generics, lambda expressions, and LINQ

I just ran across an interesting stumbling block – which forced me to figure out the problem. So, I thought I’d write it down. While using generics in an abstract base class, I wanted to be able to “inject” in the default sort order of a collection.

What I mean is, instead of having a hard-coded Order By like this:

customers.OrderBy(customer => customer.FirstName);

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

I wanted to be able to make that dynamic, make that settable later, like this – using a variable:

customers.OrderBy(orderBy);

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

But how would that “orderBy” variable be declared? Well, if you look at the signature of the .OrderBy(..), it gives you a hint:

image

Ummm. what? That doesn’t help, that makes it worse! What is TKey supposed to be? Where is that defined, and how do I find it?

I agree, this doesn’t make sense – let’s start with something easier…

Starting with .Where(..)
I was so confident that I could do this with the .OrderBy(..) because I already did it with the .Where(..) clause in LINQ. Instead of this:

customers.Where(customer => customer.FirstName.Contains('i'));

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

I could just do this:

customers.Where(whereClause);

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

And what is that variable? Again, let’s look at signature – and this is a little clearer:

image

This says the the .Where method takes in a delegate, a function pointer which MUST adhere to these rules:

  1. Takes in a Customer as a parameter
  2. Returns a Boolean

That means I can safely and easily create a variable for this, and even make it part of my method signature:

private static IEnumerable<Customer> GetByFilter(Func<Customer, Boolean> whereClause)
{
    if (whereClause == null)
        throw new ArgumentNullException("whereClause");

    IEnumerable<Customer> customers = GetAllCustomers();

    return customers.Where(whereClause);
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

This is good because now consumers of this function can use a lambda directly on this method, like this:

IEnumerable<Customer> customersFiltered = 
    GetByFilter(item=>item.LastName.Contains("i"));

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

This works easily because I know what the data type is coming in, and I know I need to return a Boolean, always. See the gist below for a full example of this.

Moving on to .OrderBy(..)
The trick here is that .OrderBy(..) works similar  EXCEPT that it doesn’t know what the return type will be. When you order on a column, LINQ doesn’t know if it’s an Int32, a String, etc. That is where that TKey comes in.

So, how then do we get around this? I tried many, many iterations and finally realized what this was actually doing. That delegate for .OrderBy(..) needs a function that takes in a Customer and returns the actual field value. The crazy part, and the part I still don’t understand, is how the generic type of the return is INFERRED!!

Check out this declaration:

private static IEnumerable<Customer> GetAndSort<TKey>(Func<Customer, TKey> orderBy)
{
    if (orderBy == null)
        throw new ArgumentNullException("orderBy");

    IEnumerable<Customer> customers = GetAllCustomers();

    return customers.OrderBy(orderBy);
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Note that you would expect to have to pass in a <TKey> when calling GetAndSort(..). But alas, when you call this function, you don’t – and it just infers the return type:

IEnumerable<Customer> customersOrdered = 
    GetAndSort(item => item.FirstName);

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

This is likely very confusing – plus, I just showed something that shouldn’t make sense. Or at least, I haven’t run across this before. This might make more sense via an example.

Putting it all together:
Imagine you have a Customer class like this:

public class Customer
{
    public Guid CustomerId { get; set; }
    public String FirstName { get; set; }
    public String LastName { get; set; }

    public override string ToString()
    {
        return String.Format("{0} {1}", FirstName, LastName);
    }
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Then, I have some methods defined like this for getting, filtering, and sorting data in a collection:

private static IEnumerable<Customer> GetAllCustomers()
{
    List<Customer> customers = new List<Customer>
    {
        new Customer() {CustomerId = Guid.NewGuid(), FirstName = "Adam", LastName = "Zelda"},
        new Customer() {CustomerId = Guid.NewGuid(), FirstName = "Carlie", LastName = "Xavier"},
        new Customer() {CustomerId = Guid.NewGuid(), FirstName = "Brianna", LastName = "Yankovic"}
    };

    return customers;
}

private static IEnumerable<Customer> GetByFilter(Func<Customer, Boolean> whereClause)
{
    if (whereClause == null)
        throw new ArgumentNullException("whereClause");

    IEnumerable<Customer> customers = GetAllCustomers();

    return customers.Where(whereClause);
}

private static IEnumerable<Customer> GetAndSort<TKey>(Func<Customer, TKey> orderBy)
{
    if (orderBy == null)
        throw new ArgumentNullException("orderBy");

    IEnumerable<Customer> customers = GetAllCustomers();

    return customers.OrderBy(orderBy);
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

and finally, code like this to consume these methods:

IEnumerable<Customer> customersRaw = GetAllCustomers();
Debug.WriteLine("Raw, unsorted:");
foreach (Customer customer in customersRaw)
{
    Debug.WriteLine(customer);
}

Debug.WriteLine("-----------------------------------");

IEnumerable<Customer> customersFiltered =
    GetByFilter(item => item.LastName.Contains("i"));

Debug.WriteLine("Customers with 'i' in their last name:");
foreach (Customer customer in customersFiltered)
{
    Debug.WriteLine(customer);
}

Debug.WriteLine("-----------------------------------");

IEnumerable<Customer> customersOrdered =
    GetAndSort(item => item.FirstName);

Debug.WriteLine("Customers sorted by first name:");
foreach (Customer customer in customersOrdered)
{
    Debug.WriteLine(customer);
}

Debugger.Break();

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

This renders output like this:

Raw, unsorted:
Adam Zelda
Carlie Xavier
Brianna Yankovic
-----------------------------------
Customers with 'i' in their last name:
Carlie Xavier
Brianna Yankovic
-----------------------------------
Customers sorted by first name:
Adam Zelda
Brianna Yankovic
Carlie Xavier

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

As you can see, filtering and sorting work. This means I was able to inject in the delegate for sorting and filtering, as a variable. When you use this technique in a base class when you don’t know what the concrete type is going to be, this becomes even more valuable.

If interested, you can DOWNLOAD the code for this post is out on the following Gist: https://gist.github.com/RobSeder/f9c8ca9041d4aa549729

Posted in General, Linq, Uncategorized

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archives
Categories

Enter your email address to follow this blog and receive notifications of new posts by email.

Join 5 other followers

%d bloggers like this: