Friday, January 22, 2010

Treating properties as reference in C# or how to modify property of class you pass to function.

Technicaly you can not pass property by reference in C#.

DoSomething(ref MyClass.MyProperty);

will not even compile.

But sometimes you want to do it badly when you refactor your code and try to put repeating paterns to a neater functions. Yes, it may be better to design your classes using Dictionary or List or some thing you can iterate easly, but this may not be under your controll, say if you get this class automaticaly generated as in case of some Web service. So, puting high matters aside, how we can do it, so it will not look too ugly.

Naturaly there are a number of ways to trick the compiler. One of the reasons it does not compute is that property is actulay a couple of functions:

class Person
{
/// <summary>
/// Gets or sets user's name.
/// </summary>
public string Name
{
get;
set;
}
/// <summary>
/// Gets or sets user's email.
/// </summary>
public string Email
{
get;
set;
}
}



It will not work like a field, but we can use Expression and Linq to work around it. We can define:

Expression<Func<Person, string>> fieldExpression


and instead of passing a field like person.Name we will pass Expression: x => x.Name
It does not looks too ugly, as yet.

The way to work with it is not too straight forward and adds some overhead to code:

First you need to "unpack it":

var expression = (MemberExpression)fieldExpression.Body;
var property = (PropertyInfo)expression.Member;



To get property you do:

string oldValue = (string)property.GetValue(person, null);


To set it:

property.SetValue(person, newValue, null);


Not too elegant and a lot of overhead. It will not work as fast as just assignemt:

person.Name = "New Name"


But it will do the job and can aid in code refactoring.

Here is a full working example that does this trick:


using System;
using System.Linq.Expressions;
using System.Reflection;

class Person
{
/// <summary>
/// Gets or sets user's name.
/// </summary>
public string Name
{
get;
set;
}
/// <summary>
/// Gets or sets user's email.
/// </summary>
public string Email
{
get;
set;
}
}

class PersonUpdater
{
static bool UpdatePersonField(
Person person,
Expression<Func<Person, string>> fieldExpression,
string newValue)
{
var expression = (MemberExpression)fieldExpression.Body;
var property = (PropertyInfo)expression.Member;
string oldValue = (string)property.GetValue(person, null);

if (string.Compare(
oldValue,
newValue,
StringComparison.OrdinalIgnoreCase) != 0)
{
property.SetValue(person, newValue, null);

return true;
}

return false;
}

static void Main()
{
Person person = new Person();

person.Name = "Old Name";
person.Email = "Old Email";

UpdatePersonField(person, x => x.Name, "New Name");
UpdatePersonField(person, x => x.Email, "New Email");
}
}