Now one possibility is to pass the value by reference which is often something I don't like very much, but won't get into that too much right now, but since we are dealing with properties here the usual more elegant way is to create a wrapper or lambda function of some kind.
Now natrually before coding anything a quick google/SO search reveals some basic solutions and as one might expect involves some reflection as one CodeProject article here.
However I usually use dynamic reflection as a last resort as there is too much room for error here and referring to fields as strings just doesn't seem like the cleanest way. Also a super simple solution is by an SO post made here (scroll down a bit) which simply just uses a small wrapper class you specifiy the getter and setter using 2 lambda expressions. This is certainly clean and simple, which I like, however I tried to create a function even simpler and more concise which also uses the compiler to ensure correctness, so I came up with this.
So much like the first version to use some reflection except no strings but using expressions. First extracting the property info from the expression, a nice clean wrapper object to help with this from this SO post:
public static class PropertyHelper<T>
public static PropertyInfo GetProperty<TValue>(
Expression<Func<T, TValue>> selector)
{
Expression body = selector;
if (body is LambdaExpression)
{
body = ((LambdaExpression)body).Body;
}
switch (body.NodeType)
{
case ExpressionType.MemberAccess:
return (PropertyInfo)((MemberExpression)body).Member;
break;
default:
throw new InvalidOperationException();
}
}
}
So taking that a little further, creating a helper class to generate a lambda getter and setter
public static class PropertyHelper
{
public static Func<TValue> GetPropertyGetter<T, TValue>(T value, Expression<Func<T, TValue>> selector)
{
var propInfo = (PropertyInfo)((MemberExpression)(selector).Body).Member;
return () => (TValue)propInfo.GetValue(value, null);
}
public static Action<TValue> GetPropertySetter<T, TValue>(T value, Expression<Func<T, TValue>> selector)
{
var propInfo = (PropertyInfo)((MemberExpression)(selector).Body).Member;
return v => propInfo.SetValue(value, v, null);
}
}
Small note: This requires you to use a lambda expression for the property any other expression will break it, I didn't add all the checks to make it more terse. So Then the usage would simply be:
var getter = PropertyHelper.GetPropertyGetter(world, w => w.Hello); var setter = PropertyHelper.GetPropertySetter(world, w => w.Hello);
And then you can get the property by calling:
getter();
setter("Hello");
Extending that a little further by adding a nice wrapper class:
public class PropertyWrapper<TValue> : IPropertyWrapper<TValue>
{
protected PropertyInfo _propInfo;
protected object _instance;
public PropertyWrapper(PropertyInfo propinfo, object instance)
{
_instance = instance;
_propInfo = propinfo;
}
public object Instance
{
get { return _instance; }
}
public TValue Value
{
get { return (TValue)_propInfo.GetValue(_instance, null); }
set
{
_propInfo.SetValue(_instance, value, null);
}
}
}
Then you can pass around the wrappers:
var wrapper = PropertyHelper.GetPropertyWrapper(world, w => w.Hello)Now you can easily use this in your generic method and manipulate any property:
private static void TestCondition(IPropertyWrapperDynamic objects are of course another way to do this however you then still are missing out on the compile time checking, so I haven't coded out this solution, but I think I'd still rather use this approach then reflection by string name method. So there you have it, this seems like a nice clean way to create property references, you can take it one more step further by creating an object extension method for this, but I usually try to avoid this as it can clutter up the code completion just a little bit too much. But if you can think of an even better cleaner way in C#condition) { condition.Value = false; Console.WriteLine(condition.Instance.Validate()); condition.Value = true; Console.WriteLine(condition.Instance.Validate()); }
No comments:
Post a Comment