Home > Programming > WPF, magic strings and some Linq love

WPF, magic strings and some Linq love

WPF as you might have experienced, is a lot more powerful as compared to WinForms when it comes to UI. However as you might have also noticed, it uses a lot more “magical strings”. e.g. if you have to show some entries in a combo box and if you have to group / sort those entries in any way, you might see code like this –

Reasons = new ListCollectionView(GetFilteredReasons(message.Reasons).ToList());

Reasons.GroupDescriptions.Add(new PropertyGroupDescription("IsCallOn"));
Reasons.SortDescriptions.Add(new SortDescription("IsCallOn", ListSortDirection.Descending));
Reasons.SortDescriptions.Add(new SortDescription("DisplayName", ListSortDirection.Ascending));

Here Reasons is an ICollectionView and IsCallOn and DisplayName are properties of Reason class whose collection is to be shown in the combo. But think what happens if you rename those properties to some other names. These property names which are strings in the above code, are not refactored. However, if these properties were used in the form of “objectName.PropertyName” form, they would’ve been promptly renamed when you refactor your code.

I had read the “strongly typed reflection” post from Daniel Cazzulino long back but was not clear on how to implement such stuff. I had also read the Functional .Net – Lose the magic strings post from Mathew Podwysocki on how the Asp.Net MVC gets rid of magic strings. With the help of these two posts I was finally able to write the below code – 

public static class ListCollectionViewExtensions
{
	public static ICollectionView GroupByOn<T>(this ICollectionView collection, Expression<Func<T, object>> expression)
			where T : class
	{
		var propertyName = Reflector.GetPropertyName(expression);
		collection.GroupDescriptions.Add(new PropertyGroupDescription(propertyName));
		return collection;
	}

	public static ICollectionView SortByOn<T>(this ICollectionView collection, Expression<Func<T, object>> expression, ListSortDirection sortDirection)
		where T : class
	{
		var propertyName = Reflector.GetPropertyName(expression);
		collection.SortDescriptions.Add(new SortDescription(propertyName, sortDirection));
		return collection;
	}
}

public class Reflector
{
	public static string GetPropertyName<T>(Expression<Func<T, object>> expression)
	{
		var property = GetProperty(expression);
		return property.Name;
	}

	private static MemberInfo GetProperty<T>(Expression<Func<T, object>> expression)
	{
		var lambdaEx = expression as LambdaExpression;
		if (lambdaEx == null) throw new ArgumentNullException("expression");

		MemberExpression memberExpression = null;

		if (lambdaEx.Body.NodeType == ExpressionType.Convert)
		{
			var unaryExpression = lambdaEx.Body as UnaryExpression;
			if (unaryExpression == null) throw new ArgumentNullException("expression");

			if (unaryExpression.Operand.NodeType == ExpressionType.MemberAccess)
			{
				memberExpression = unaryExpression.Operand as MemberExpression;
			}
		}
		else if (lambdaEx.Body.NodeType == ExpressionType.MemberAccess)
		{
			memberExpression = lambdaEx.Body as MemberExpression;
		}

		if (memberExpression == null) throw new ArgumentNullException("expression");
		return memberExpression.Member;
	}
}

Which makes use of Expression<> class from the System.Linq.Expressions namespace. So, instead of writing code like above, one can write the below code, which is type safe and resharper friendly.

Reasons = new ListCollectionView(GetFilteredReasons(message.Reasons).ToList());

Reasons
	.GroupByOn<Reason>(x => x.IsCallOn)
	.SortByOn<Reason>(x => x.IsCallOn, ListSortDirection.Descending)
	.SortByOn<Reason>(x => x.DisplayName, ListSortDirection.Ascending);

So not only are we writing less code than before, but we’ve also saved ourselves from possible future refactoring headaches 🙂 And as usual, here are the unit tests for checking the ListCollectionViewExtensions class –

[TestFixture]
public class As_a_ListCollectionViewExtensions
{
	[Test]
	public void should_add_group_by_clause_on_an_int_property()
	{
		var list = GetSampleData();
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(0));

		list.GroupByOn<SampleClass>(x => x.Id);
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(1));
	}

	[Test]
	public void should_add_group_by_clause_on_a_bool_property()
	{
		var list = GetSampleData();
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(0));

		list.GroupByOn<SampleClass>(x => x.IsGood);
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(1));
	}

	[Test]
	public void should_add_group_by_clause_on_a_string_property()
	{
		var list = GetSampleData();
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(0));

		list.GroupByOn<SampleClass>(x => x.Name);
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(1));
	}

	[Test]
	public void should_add_group_by_clause_on_a_char_property()
	{
		var list = GetSampleData();
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(0));

		list.GroupByOn<SampleClass>(x => x.CharProp);
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(1));
	}

	[Test]
	public void should_add_group_by_clause_on_a_byte_property()
	{
		var list = GetSampleData();
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(0));

		list.GroupByOn<SampleClass>(x => x.ByteProp);
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(1));
	}

	[Test]
	public void should_add_group_by_clause_on_a_sbyte_property()
	{
		var list = GetSampleData();
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(0));

		list.GroupByOn<SampleClass>(x => x.SByteProp);
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(1));
	}

	[Test]
	public void should_add_group_by_clause_on_a_double_property()
	{
		var list = GetSampleData();
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(0));

		list.GroupByOn<SampleClass>(x => x.DoubleProp);
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(1));
	}

	[Test]
	public void should_add_group_by_clause_on_a_long_property()
	{
		var list = GetSampleData();
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(0));

		list.GroupByOn<SampleClass>(x => x.LongProp);
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(1));
	}

	[Test]
	public void should_add_group_by_clause_on_a_decimal_property()
	{
		var list = GetSampleData();
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(0));

		list.GroupByOn<SampleClass>(x => x.DecimalProp);
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(1));
	}

	[Test]
	public void should_add_group_by_clause_on_a_float_property()
	{
		var list = GetSampleData();
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(0));

		list.GroupByOn<SampleClass>(x => x.FloatProp);
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(1));
	}

	[Test]
	public void should_add_group_by_clause_on_a_short_property()
	{
		var list = GetSampleData();
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(0));

		list.GroupByOn<SampleClass>(x => x.ShortProp);
		Assert.That(list.GroupDescriptions.Count, Is.EqualTo(1));
	}

	private static ICollectionView GetSampleData()
	{
		var list = new List<SampleClass>()
		       	{
		       		new SampleClass()
		       			{
		       				Id = 1,
		       				IsGood = true,
		       				Name = "hi",
						ByteProp = 255,
						SByteProp = 127,
		       				CharProp = 'y',
		       				DoubleProp = 20.2d,
		       				LongProp = 4294967296,
		       				DecimalProp = 9898.34m,
		       				FloatProp = 234.567f,
		       				ShortProp = (short) 5
		       			}
		       	};

		return new ListCollectionView(list);
	}

	private class SampleClass
	{
		public int Id { get; set; }
		public bool IsGood { get; set; }
		public string Name { get; set; }

		public byte ByteProp { get; set; }
		public byte SByteProp { get; set; }
		public char CharProp { get; set; }
		public double DoubleProp { get; set; }
		public long LongProp { get; set; }
		public decimal DecimalProp { get; set; }
		public float FloatProp { get; set; }
		public short ShortProp { get; set; }
	}
}

Hope you like it.

Advertisements
Categories: Programming

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

%d bloggers like this: