Avoid Repeating Yourself Using Delegates and Anonymous Functions

Quite a few acronyms are tossed around in the development community - SOLID, DRY, and KISS are but a few.  Today I want to focus a little bit on the DRY principle, Don't Repeat Yourself.  Often we find ourselves writing the same piece of code over and over in an application, only changing a small bit - a class name or method name for example. We use copy and paste liberally. Then we need to make a change in the process we've copy and pasted all over the place - have we changed them all?

This brings us to the idea of delegates. Delegates are, basically, .NET forms of function pointers (Yes!  I said the word pointer and .NET in the same sentence).  You can take a delegate and assign it to a variable. You can take that variable and pass it around functions like any other .NET variable, yet execute it as a function call.  When I realized this, a light went off in my head!  I can pass a function call into another function and avoid having to repeat myself again and again

Delegates are one thing.  In older versions of .NET you were forced to declare the delegate in the class you were working in before you could use it in code.  Starting with newer versions of .NET, anonymous function can be used as delegates.  There are two types of anonymous functions available

Action<T1 ... T16> - A function with no return value ("Subs" in Visual Basic)
Func<T1 ... T16, TResult> - A function that returns a TResult

For example,

Action<string, IUserRole, string>

is an anonymous function that takes a string, IUserRole, and string as parameters and returns nothing (A Sub in Visual Basic)

Likewise,

Func<string, string, IEnumerable<IUserRole>>

is an anonymous function that takes a string and a string as parameters and returns an IEnumerable<IUserRole>. There are overloads for up to 16 parameters but if you're passing 16 parameters to a function, you should really rethink that and maybe use a struct or class :)

Let's dig into some practical applications of this...

Revisiting the user role copy application I spoke of before, I had a need to perform the same basic operations - log the roles, delete the roles, copy the roles, but across 5 separate systems, each with their own role structure and tables. I could write what amounts to the same code 5 times, or I could put the power of .NET to work and use some of the language features available.

For example, I needed to delete a users' roles across a number of environments, but log them beforehand for auditing purposes.

public void DeleteExistingRoles(IEnumerable<string> environments,
								string userName, string roleType, 
                                Func<string, string, IEnumerable<IUserRole>> getRoleData,
								Action<string, string> deleteRoles)
{
	foreach (var environment in environments)
	{
		try
		{
			LogData(getRoleData, "Delete role", userName, roleType, environment);
			deleteRoles(userName, environment);
		}
		catch (Exception exception)
		{
			LogError(exception);
		}
	}
}

public void LogData(Func<string, string, IEnumerable<IUserRole>> getRoleData,
					string recordType, string userName,
					string roleType, string environment)
{
	foreach (var role in getRoleData(userName, environment))
	{
		// Log the user role data
	}
}

There are a couple of things going on here. First we have a function that takes both Func<> and Action<> method.  That function then passes on the delegates to another function in order to do the work!  Rather than having to write a logging function for Application A and another for Application B, I can just pass in the function that gets the roles, then log them using one routine.

How do you invoke the code?  Well I'm glad you asked!  Somewhere out there are real functions that actually do the work.  In order to get the real functions in, you pass in lambdas as seen below

var app1Repo = new App1Repository();
var actions = new UserActions();

actions.DeleteExistingRoles(environments,
							destinationUser,
							roleType,
							(u, e) => app1Repo.GetRoles(u, e),
							(u, e) => app1Repo.DeleteRoles(u, e));

We instantiate the given data repository. We then instantiate our class that is going to do the work. Then we pass in the methods that are going to do the work as described in the functions.  Think of the power here - as long as whatever function we're going to use matches the signature of the delegate, we can pass it into the function and have it executed as if we were calling it directly.  I can use that DeleteExistingRoles() function to do work on as many applications as I have GetRoles() and DeleteRoles() functions for.

Let's extend this concept a little further and show you the basics of the cloning of user roles method I created (shortened for the sake of clarity)

public void CloneRoles(IEnumerable<string> environments, string roleType,
					string sourceUser, string destinationUser,
					Action<string, IUserRole, string> insertRole,
					Func<string, string, IEnumerable<IUserRole>> getRoleData)
{
	foreach (var environment in environments)
	{
		var destinationRoles = getRoleData(destinationUser, environment).ToList();
		
		LogData(getRoleData, "Source role", sourceUser, roleType, environment);
		LogData(getRoleData, "Original Destination Role", destinationUser, roleType, environment);
		
		foreach (var role in getRoleData(sourceUser, environment))
		{
			if (!destinationRoles.Any(r => r.Equals(role)))
			// If the role already exists, don't insert it again
			{
				try
				{
					insertRole(destinationUser, role, environment);
					destinationRoles.Add(role);
				}
				catch (Exception exception)
				{
					LogError(exception);
				}
			}
		
			LogData(getRoleData, "Final Destination role", destinationUser, roleType, environment);
		}
	}
}

An Action<> is passed in to perform the writing of the roles, and a Func<> that returns an IEnumerable<IUserRole> is passed in to get the roles for the given user.  The main reason for passing using delegates instead of just passing in a list of roles for a given user we created in another method is that we need to get the role data before the cloning happens, get the roles for the cloning, then get the roles again after it's done.  By using delegates we can neatly wrap all of that functionality up within the cloning method.

Using the power of delegates and anonymous functions allows to not only write cleaner looking code, but to avoid repeating yourself and thus write less error-prone code.

Using LINQ With WebForms Controls

On a recent project here at the office, I was asked to build a page that let our QA department copy access roles from one user to another across multiple companies, applications, and environments. Based on the UI design that the project people had conceived, I ended up with a repeater full of checkboxes for the companies and a grid system of checkboxes for the applications and environments.

Given that controls are all stored in collections, I figured I could use something other than loops and if statements to manipulate these structures.  The power of LINQ against not just SQL, but any type of collection has always fascinated me so I wanted to see if I could use LINQ to manipulate the control collections of WebForm controls.  You can't use LINQ straight up against the collections, but with an additional method call, the world of LINQ is opened to you!

Let's start out by finding out how many checkboxes in this repeater are checked.

var count = this.CorporationListRepeater.Items
                .Cast<RepeaterItem>()
                .Count(r => ((CheckBox)r.FindControl("chkCorporation")).Checked);

From there, it was a matter of retrieving the values of these checkboxes when the roles were processed.  

foreach (var item in this.CorporationListRepeater.Items
                         .Cast<RepeaterItem>()
                         .Where(r => ((CheckBox)r.FindControl("CorporationCheckBox")).Checked))
{
    corporationList.Add(((CheckBox)item.FindControl("CorporationCheckBox")).Text);
}

Simple example?  Sure - but it leads you to more LINQable items in WebForms

In the above examples, you see there's an extra step that needs to be taken in order to use LINQ against these collections - the Cast() method. As explained by this entry in MSDN, the Cast() method enables the standard query operators to be invoked on non-generic collections by supplying the necessary type information.

Let's dig a little deeper

For the grid of applications and environments, I ended up with 5 rows of applications and 3 rows of environments.  Some items could be checked, others not.  Add in to that a requirement that security be set up so that another department who manages some of the applications not be able to affect the other applications and the form becomes that much more complicated.

So let's say I have a Panel with some controls in it, namely a number of checkboxes that represent the individual environments for the given application represented by that row.  Assume that if the user cannot access that particular application, the checkboxes are disabled.  I can build a list of selected environments with LINQ

foreach (var selectedItem in environments.Controls
                                         .Cast<Control>()
                                         .Where(c => (c is CheckBox) && 
                                                    ((CheckBox)c).Enabled && 
                                                    ((CheckBox)c).Checked))
{
        selectedEnvironments.Add(((CheckBox)selectedItem).Text);
}

After they're done with the process, I needed to reset all of disabled fields on the screen, I could quickly do that for all of the controls in all of the panels on the page

foreach (var panelItem in container.Controls
                                   .Cast<Control>()
                                   .Where(c => c is Panel))
{
     foreach (var itemControl in panelItem.Controls
                                          .Cast<Control>())
     {
          if (itemControl is Button)
          {
               ((Button)itemControl).Enabled = value;
          }
          else if (itemControl is DropDownList)
          {
               ((DropDownList)itemControl).Enabled = value;
          }
          else if (itemControl is CheckBox)
          {
               ((CheckBox)itemControl).Enabled = value;
          }
     }
}

This is just a small sampling of the power of LINQ against WebForm custom control collections.  The next time you are looking at looping through controls with lots of if statements and whatnot, give LINQ a try!

LINQ GroupBy In VB is Different Than C# Due to Anonymous Type Differences

I'm a C# programmer at heart.  I have been for many years now.  The place I work at, however, is a Visual Basic shop. The transition from C# to Visual Basic isn't really hard - .NET is, for the most part, .NET regardless of the language. With language parity a phrase being bandied about (kind of like parity in the NFL), you can build the same things in VB that you can in C#. 

Knowing this, and experiencing it for the past almost two years at my place of employment, has made writing Visual Basic code a piece of cake. The other day though, I spent a few hours trying to figure out just exactly why the following piece of code didn't work as I expected.  Pardon the contrived example, but ...

Public Class TestItem

	Public Property Category As String
	Public Property SubCategory As String
	Public Property ItemCount As Integer

	Public Sub New()
	End Sub
	
	Public Sub New(ByVal newCategory As String, ByVal newSubCategory As String, ByVal newItemCount As Integer)
	
		Me.Category = newCategory
		Me.SubCategory = newSubCategory
		Me.ItemCount = newItemCount
		
	End Sub
	
End Class

Public Sub Main
	
	Dim testList = New List(Of TestItem)
	
	testList.Add(New TestItem("A", "AA", 5))
	testList.Add(New TestItem("A", "AA", 10))
	testList.Add(New TestItem("A", "BB", 15))
	testList.Add(New TestItem("A", "BB", 20))
	testList.Add(New TestItem("B", "CC", 20))
	testList.Add(New TestItem("C", "DD", 30))
	
	Dim itemList = testList.GroupBy(Function(t) New With 
												{ 
													t.Category,
													t.SubCategory 
												}) 
						   .Select(Function(s) New With 
						   					   { 
											   		s.Key.Category,
													s.Key.SubCategory,
													.Total = s.Sum(function(i) i.ItemCount)
											   })
	
	For Each item In itemList
		Debug.WriteLine(String.Format("{0} / {1} - {2}", item.Category, item.SubCategory, item.Total))
	Next
	
End Sub

 

Should be simple and straightforward, right?  Group by the category and subcategory then sum up the count field. Needless to say I was flabbergasted when that code yielded the following

A / AA - 5
A / AA - 10
A / BB - 15
A / BB - 20
B / CC - 20
C / DD - 30

That is absolutely not the result I wanted.  The AAs and BBs should be grouped together!  I've written this same piece of code many, many times in C# 

public void Main()
{
	var testList = new List<TestItem>();
	
	testList.Add(new TestItem("A", "AA", 5));
	testList.Add(new TestItem("A", "AA", 10));
	testList.Add(new TestItem("A", "BB", 15));
	testList.Add(new TestItem("A", "BB", 20));
	testList.Add(new TestItem("B", "CC", 30));
	testList.Add(new TestItem("C", "DD", 40));

	var itemList = testList.GroupBy(t => new 
										 { 
										 	t.Category,
											t.SubCategory
										 })
						   .Select(s => new
						   				{ 
						   					s.Key.Category,
											s.Key.SubCategory, 
											Total = s.Sum(i => i.ItemCount) 
										});

	foreach (var item in itemList)
	{
		Debug.WriteLine(String.Format("{0} / {1} - {2}", item.Category, item.SubCategory, item.Total));
	}
}

Which, when you run it, spits out the expected results

A / AA - 15
A / BB - 35
B / CC - 30
C / DD - 40

The New keyword inside the GroupBy() creates a new anonymous type.  In Visual Basic, each instance of that type has a unique hash code generated for it which is used by the Equals() method.

That can be verified by creating a new anonymous type with a LINQ Select() and looping through the results

Dim itemList = testList.Select(function(t) New With
											   {
											    	t.Category,
													t.SubCategory
											   })
	
	For Each item In itemList
		Debug.WriteLine(String.Format("{0} / {1} / HashCode = {2}", item.Category, item.SubCategory, item.GetHashCode()))
	Next

Which yields the results

A / AA / HashCode = 57488007
A / AA / HashCode = 36823492
A / BB / HashCode = 46444411
A / BB / HashCode = 9623713
B / CC / HashCode = 30413906
C / DD / HashCode = 26448518

You can see the problem - even though the properties are the same, the hash codes are unique per object - which leads to our grouping problem where each object needs to be equal in order for GroupBy() to function as we would expect.

Running the same code in C# yields

A / AA - HashCode = 391263192
A / AA - HashCode = 391263192
A / BB - HashCode = 391197655
A / BB - HashCode = 391197655
B / CC - HashCode = 1912266413
C / DD - HashCode = -861632125

Anonymous types in C# override GetHashCode() and Equals() to use the values of the object's properties in the calculations. If two objects contain the same property values, then they are considered equal.  This behavior can be duplicated in Visual Basic by using the modifier Key on the properties of the anonymous type.  Microsoft explains the details at http://msdn.microsoft.com/en-us/library/bb531349.aspx

 

Dim itemList = testList.GroupBy(Function(t) New With 
												{ 
													Key t.Category,
													Key t.SubCategory 
												}) _
						   .Select(Function(s) New With 
						   					   { 
											   		s.Key.Category,
													s.Key.SubCategory,
													.Total = s.Sum(function(i) i.ItemCount)
											   })

Running THAT code yields the results you would expect

A / AA - 15
A / BB - 35
B / CC - 20
C / DD - 30

Running this code

Dim itemList = testList.Select(function(t) New With
											   {
											    	Key t.Category,
													Key t.SubCategory
											   })

Yields the results you would expect

A / AA / HashCode = -132090043
A / AA / HashCode = -132090043
A / BB / HashCode = -132155580
A / BB / HashCode = -132155580
B / CC / HashCode = 1388913178
C / DD / HashCode = -1384985360

Each instance of the type with the same property values results in an equal hash code.

I know I learned something new here.  I hope someone else can benefit from this.

Welcome!

Welcome to Fumarin's Place!

 

Yeah, cheesy name and a cheesy theme, but what can I say?  I'm a .NET Developer by trade and while diligently working at that trade, I oftentimes find myself running up against some issue or coming across something cool so I decided to try my hand at this whole blogging thing to share what I find.  I'm easily distracted so I may not update this often but I will try to do what I can.

Welcome, be nice, and take it easy on me!

About Me

My name is Mike Curtis and I'm a geek, a cigar smoker, and a scotch drinker.  If I can find way to combine all three, fantastic!  I've been doing this IT thing for over 20 years and want to share some of the solutions to problems I've come across in the real world. Because, as they say, sharing is caring.

Month List

Page List