Saturday, October 24, 2009

Linqify your legacy webapplications: five real-world refactor examples

Edit notes:
- Edited examples based on Bart's feedback (see comments).

This post is meant for developers who are upgrading legacy webapplications to .NET 3.5 and for developers who know Linq, but have a hard time finding scenarios to apply Linq.

I got my inspiration for this post by reading Professional Refactoring in C# & ASP.NET (Wrox Programmer to Programmer).

I downloaded the My Web Pages Starter Kit to find some examples, which is written for .NET 2.0. I upgraded it to .NET 3.5 to use Linq.

Below you can find some real-world examples of refactoring to use Linq.

Querying collections never has been this clean!


Example one

Before

   1:  bool blnTravelDiary = false;
   2:  foreach (string theme in themes) {
   3:      if (Path.GetFileName(theme) == "TravelDiary")
   4:              blnTravelDiary = true;
   5:  }


After

   1:  bool blnTravelDiary = themes.Any(theme => Path.GetFileName(theme) == "TravelDiary"); 



Example two

Before

   1:  foreach (RoleData data in _roles) {
   2:      if (data.RoleName.Equals(roleName, StringComparison.CurrentCulture))
   3:              return true;
   4:  }
   5:  return false;


After

   1:  bool exists = _roles.Any(roleData => roleData.RoleName.Equals(roleName, StringComparison.CurrentCulture));



Example three

Before

   1:  foreach (RoleData data in _roles) {
   2:      if (data.RoleName == rolename) {
   3:              found = data;
   4:                      break;
   5:           }
   6:  }

   7:  return found;


After

   1:  var found = from RoleData data in _roles
   2:              where data.RoleName == rolename
   3:              select data;
   4:  return found.FirstOrDefault();



Example four

Before

   1:  List<string> foundRoles = new List<string>();
   2:  foreach (RoleData data in _roles){
   3:      if (data.Users.Contains(username))
   4:              foundRoles.Add(data.RoleName);
   5:  }
   6:  return foundRoles.ToArray();


After

   1:  var data = from RoleData roleData in _roles
   2:             where roleData.Users.Contains(username)
   3:             select roleData.RoleName;
   4:  return data.ToArray();



Example five

Before

   1:  foreach (MembershipUser user in Membership.GetAllUsers())
   2:  {
   3:      if (string.Compare(user.UserName, txtUserName.Text, true) == 0)
   4:          {
   5:              Login1.UserName = user.UserName;
   6:                     return;
   7:          }
   8:  }



After

   1:  var matchingUsers = from MembershipUser user in Membership.GetAllUsers()
   2:                      where string.Compare(user.UserName, txtUserName.Text, true) == 0
   3:                     select user.UserName;
   4:   
   5:  var firstMatch = matchingUsers.FirstOrDefault();
   6:   
   7:  if (firstMatch != null) {
   8:      Login1.UserName = firstMatch;
   9:  }

3 comments:

  1. Things definitely get cleaner using LINQ. Nonetheless, a few pieces of constructive feedback.

    One first thing to keep in mind is to preserve semantics. For example, breaking out of a loop is not the same as a query that performs a Count which is subsequently compared to 0. Keep in mind the various lambda expressions passed to query operators could be side-effecting...

    Examples 1 and 2 could be done more concisely using the Any operator, e.g.:

    _roles.Any(data => data.RoleName.Equals(roleName, StringComparison.CurrentCulture))

    Beware of using == versus Equals; the latter can null reference where the code didn't before. You may be changing semantics by using Equals.

    Cheers,
    -Bart

    ReplyDelete
  2. Thanks for the feedback Bart! :)

    ReplyDelete
  3. Fixed the examples based on your comment Bart. The Any ext method makes a lot more sense!

    ReplyDelete