2010-01-06

Catch property, field and method name changes compile time in dotnet

When you want to populate a dropdown listbox with customers

class Customer
{
int Id;
string Name;
...
}
and have to write

myDropdownListbox.DisplayMember = "Name";
myDropdownListbox.DataMember = "Id";
or

myGridColumnCustomerName.DatatMeber = "Name";
you really get a sour taste in your mouth.

Having magic strings like above spread in your code is really bad since it always compiles but might fail runtime. This means more manual tests. Which in turns means a certain friction against renaming properties while refactoring.
I have been in way too many projects where one table has a field ItemNumber when it should have been ItemCount and where some fields are called Nr while another Number and these names are spread up in the layers to reach the presentation layer where they surface as magic strings like the examples above.

Luckily there is a solution in Dotnet 3 with LINQ. It isn't the prettiest but it fails compile time when it should and that is considered a good thing.

( I won't bother with explanation - just read the code. )

// The Code.
class ReflectionUtility
{

public static string GetPropertyName<T,TReturn>(Expression<Func<T,TReturn>> expression)
{
MemberExpression body = (MemberExpression)expression.Body;
return body.Member.Name;
}

public static string GetMethodName<T,TReturn>(Expression<Func<T,TReturn>> expression)
{
var body = expression.Body as UnaryExpression;
var operand = body.Operand as MethodCallExpression;
var argument = operand.Arguments[2] as ConstantExpression;
var methodInfo = argument.Value as System.Reflection.MethodInfo;

return methodInfo.Name;
}

}

// The test code.
class MyClass
{
public int MyField;
public int MyPublicProperty { get; set; }
public string MyReadonlyProperty { get { return string.Empty; } }
public int MyMethod() { return 0; }

private MyClass() { } // To make sure the class doesn't need a default constructor.
}

class Program
{
static void Main(string[] args)
{
string fieldName = ReflectionUtility.GetPropertyName((MyClass x) => x.MyField);
Console.WriteLine(string.Format("MyClass.MyField:{0}", fieldName));
Debug.Assert("MyField" == fieldName);

string propertyName = ReflectionUtility.GetPropertyName((MyClass x) => x.MyPublicProperty);
Console.WriteLine(string.Format("MyClass.MyPublicProperty:{0}", propertyName));
Debug.Assert("MyPublicProperty" == propertyName);

propertyName = ReflectionUtility.GetPropertyName((MyClass x) => x.MyReadonlyProperty);
Console.WriteLine(string.Format("MyClass.MyReadonlyProperty :{0}", propertyName));
Debug.Assert("MyReadonlyProperty" == propertyName);

string methodName = ReflectionUtility.GetMethodName<MyClass, Func<int>>((MyClass x) => x.MyMethod);
Console.Write(string.Format("MyClass.MyMethod:{0}", methodName));
Debug.Assert( "MyMethod" == methodName);

Console.Write(Environment.NewLine + "Press any key.");
Console.ReadKey();
}

}
Honor those who should.

Update: When copy-pasting through manoli.net some angle brackets got dropped. This is, hopefully, fixed now.  Otherwise - get the source here: http://selfelected.pastebin.com/f77563a02

2 comments:

mblund said...

Is it just my browser or can someone else see the full width of the source code.

LosManos said...

With my 3D screen I was able to look behind the covering letters and didn't notice the missing text.

I apologise for this so do like this: mark the text and copy it to your favourite editor.

I also copied the code to paste bin here: http://selfelected.pastebin.com/f77563a02