There is a really cool pattern used by many GUI frameworks to ensure things are coded correctly:
interface IBase1 {}
interface IBase2 {}
class Base1 : IBase1
{
public int x { get; set; }
}
class Base2 : IBase2
{
public int y { get; set; }
}
static class Helpers
{
public static void ToProp<T,Y> (this T obj, Func<T, Y> getter)
{
}
}
class Program
{
static void Main(string[] args)
{
var b1 = new Base1();
var b2 = new Base2();
b1.ToProp(b => b.x);
b2.ToProp(b => b.y);
}
}
The wonderful thing here is that when you type b => b.x, Visual Studio will give you IntelliSense, and the compiler will complain if you tried to access an incorrect property. I see this used often in MVVM frameworks. They will often take the b => b.x as an expression tree and parse out the name of the parameter in order to properly raise the notify property change message.
I would like to extend this, and replace the ToProp definition with the following, basically having two code paths depending on the base interface:
static class Helpers
{
public static void ToProp<T,Y> (this T obj, Func<T, Y> getter)
where T : IBase1
{
// Do something custom for 1
}
public static void ToProp<T, Y>(this T obj, Func<T, Y> getter)
where T : IBase2
{
// Do something custom for 2
}
}
This won't compile as is - the two ToProp invocations both fail with ambiguous method resolution errors. This is a well known problem on SO - object constraints are not part of the method resolution process (see Lipert's blog for example).
But I can't help but wonder if there isn't a way. I tried replacing this T obj with this Base1 obj, for example, but in that case you loose the ide's support for property resolution, also it would be possible to write b1.ToProp(b => b.y). That could be caught with a runtime exception, I suppose. I tried implicit conversions too - but that isn't part of the method resolution process, unfortunately.
This came up because I was extending the ReactiveUI framework to work with Caliburn.Micro. ReactiveUI has this very nice extension method ToProperty which takes a ReactiveUI ViewModel. With minor modifications I can alter that code to use a Caliburn.Micro view model. However, I then run into the ambiguous method issues above. In the meantime I just call the Caliburn.Micro method ToPropertyCM.
Anyone know a clever avenue I should chase to make something like this work? And be extensible to new base class types?
Edited Fixed up example to show that it is a simple interface that I'm interested in. BTW, I tried the wrapper method b.c. if I understand method resolution it should allow you to do constraint checking on the types of a template argument as part of the resolution process. But, as I mentioned, the implicit type conversion doesn't work during resolution.
Just don't make the methods generic with respect to that type:
public static void ToProp<Y>(this Base1 obj, Func<Base1, Y> getter)
{
// Do something custom for 1
}
public static void ToProp<Y>(this Base2 obj, Func<Base2, Y> getter)
{
// Do something custom for 2
}
If it's important that these methods be generic with respect to that type, then you'll need to change the signature in some way to resolve the ambiguity. The most effective way of doing this would be to change the name. If the behavior is being personalized for those two types then they're conceptually doing something at least slightly different, so you should be able to reflect that in the name of the method.
As you mentioned that you are actually only interested in parsing the expression to get the property name, I’ll present you a different method instead of focusing on your code which, as the discussion on Servy’s answer showed, won’t really work.
So, I do this myself a lot for MVVM. My view models implement INotifyPropertyChanged and to raise the PropertyChanged event, I need to specify the name of the property that changed in the event arguments. As this is a string, there is no inherent check that the property name is actually correct. So just like you said, I use a lambda expression to specify the property in a type-safe matter (with IntelliSense support), and then parse the expression tree to extract the property name.
As this is something I only need with INotifyPropertyChanged, I have the implementation in my base view model which implements both the interface and a quick method for raising the event with a lambda expression.
So I don’t actually use an extension method. This has the benefit, that I don’t need to know the type of the property owner. For example if I want to throw the event for something.Name, I don’t need to know what type something is. Instead of running this:
viewModel.OnPropertyChanged(viewModel.GetPropertyNameFor(vm => vm.Name));
I just do
viewModel.OnPropertyChanged(() => viewModel.Name);
// or actually
this.OnPropertyChanged(() => Name);
So the expression we are looking at looks like this: () => obj.Property. That is a Expression<Func<T>> where T is the type of the property—something that doesn’t interest us actually.
The extraction actually happens in a static method and looks like this:
static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
{
if (propertyExpression == null)
throw new ArgumentNullException("propertyExpression");
MemberExpression memberExpression = propertyExpression.Body as MemberExpression;
if (memberExpression == null)
throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
PropertyInfo property = memberExpression.Member as PropertyInfo;
if (property == null)
throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
return memberExpression.Member.Name;
}
And that’s already all there is to do:
var obj = new {
Foo = 123,
Bar = "baz"
};
Console.WriteLine(ExtractPropertyName(obj.Foo)); // Foo
Console.WriteLine(ExtractPropertyName(obj.Bar)); // Bar
All that follows is just some helper methods in the base view model that allow calling OnPropertyChanged(Expression<Func<T>> propertyExpression) etc.
You can actually make that function an extension method just by changing its signature:
static string ExtractPropertyName<T> (this INotifyPropertyChanged obj, Expression<Func<T>> propertyExpression)
{ … }
Then you can call the method on any object that implements INotifyPropertyChanged which your framework very likely does.
Your example could look like this then:
var b1 = new Base1();
var b2 = new Base2();
// in a static Utils class
Utils.ExtractPropertyName(() => b1.x);
// or as an extension method to INPC
someViewModel.ExtractPropertyName(() => b2.y);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With