Skip navigation

There’s a new oxymoron going around call Static Reflection. The basic gist is that by using expression trees you can do things that appear to be dynamic but in reality are checked at compile time. Like for instance, getting the name of a property for firing an INotifyPropertyChanged.PropertyChanged event. My colleague, Jon Fuller, showed me his code for Method Guards and my first comment was that this would be a great tool for implementing INotifyPropertyChanged. He informed me that he already went there and showed me that code as well.

I wasn’t too happy with the idea of wasting your sole base class on NotifyPropertyChanged so I suggested that we use extension methods instead. After a bit of finagling with the framework (Events don’t like to be fired from outside their declaring class), here is the result:

        public static void SetProperty<T>(this INotifyPropertyChanged source, 
						Expression<Func<T>> propExpr, 
						Expression<Func<T>> fieldExpr, 
						T value)
        {
            source.SetProperty(propExpr, fieldExpr, value, () => { });
        }

        public static void SetProperty<T>(this INotifyPropertyChanged source, 
						Expression<Func<T>> propExpr, 
						Expression<Func<T>> fieldExpr, 
						T value, 
						Action doIfChanged)
        {
            var prop = (PropertyInfo)((MemberExpression)propExpr.Body).Member;
            var field = (FieldInfo)((MemberExpression)fieldExpr.Body).Member;
            var currVal = (T)prop.GetValue(source, null);
            if (currVal==null && value==null)
                return;
            if (currVal==null || !currVal.Equals(value))
            {
                field.SetValue(source, value);
                var eventDelegate =
			(MulticastDelegate) source.GetType().GetField(
				"PropertyChanged",
				BindingFlags.Instance | BindingFlags.NonPublic).
				GetValue(source);
                Delegate[] delegates = eventDelegate.GetInvocationList();
                var args = new PropertyChangedEventArgs(prop.Name);
                foreach (Delegate dlg in delegates)
                {
                    dlg.Method.Invoke(dlg.Target, new object[] { source, args });
                }
                doIfChanged();
            }
        }

Now you can use a strongly typed NotifyPropertyChanged declaration without using your base class

    public class PersonStaticReflection : INotifyPropertyChanged
    {
        private string _firstName;
        private string _lastName;
        public string FirstName
        {
            get { return _firstName; }
            set { this.SetProperty(() => FirstName, () => _firstName, value); }
        }
        public string LastName
        {
            get { return _lastName; }
            set
            {
                this.SetProperty(() => LastName,
                                 () => _lastName,
                                 value,
                                 () =>
                                     {
                                         // do something useful here
                                     });
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

For those concerned, you can statically cache the PropertyChanged field in a dictionary mapped to the object type so that you only have to reflect on it one time.

Advertisements

6 Comments

  1. Michael, Michael, Michael….Have I taught you nothing? INotifyPropertyChanged is obsolete. In fact, I am writing an article with that title for CoDe Magazine.Still, nice trick. I could see some uses for this technique in places that are *not* already obsoleted by superior technology :).

  2. Curious to understand what you mean INPC is obsolete? I know there are dependency properties and the like but some scenarios (Silverlight for example) getting dependency properties are expensive. But while we\’re talking about it, DP registration can be done using this method as well….I thinklet me get back to you on that 😉

  3. I was referring to Update Controls, not dependency properties. You commented on a couple of the videos that I posted (Presentation Model and Navigation Model). Update Controls replaces data binding so you never have to use INotifyPropertyChanged again.Anyway, back to the subject at hand. Have you compared this technique for getting the property name with using StackTrace? For example: new StackTrace().GetFrame(1).GetMethod().Name.Substring(4). Sure, you are doing more than just getting the property name with lambdas, but using the stack trace avoids one possible source of errors. Balance the reduction in code against the poor performance of reflection. It\’s worth measuring.

  4. Nice implementation.One thing to be careful of – by pulling on the underlying backing field of the event, you are taking a dependency on an undocumented implementation of the C# compiler, ie that it stores the handlers of an event in a field with the same name. Above, for example, would not work in a VB/C++ generated type or with a custom implementation of a C# event. It is also free to change this fact, however unlikely, in a new version of the compiler or even in a service pack.By-the-way, I\’m not sure why you think that \’static reflection\’ is an oxymoron – this is effectively what Reflection-Only loads of assemblies is. The act of \’reflecting\’ is usually static in tools such as FxCop, etcRegardsDavid Keanhttp://davesbox.com

  5. Good observation David, I guess that wuld be a limitation. Also, it won\’t work in partial trust because it uses private reflection. So for now I\’m labelling it as a cool trick. Until I can do some research on the performance implications.

  6. A little late to the party, but you can avoid some unnecessary reflection and improve the reliability of your design with a couple minor adjustments, like having the invoker provide the event subscriber(s):public static class Extensions{ public static void SetProperty<T>( this INotifyPropertyChanged source, Expression<Func<T>> propertyResolver, Expression<Func<T>> fieldResolver, T value, PropertyChangedEventHandler propertyChangedHandler) { source.SetProperty(propertyResolver, fieldResolver, value, propertyChangedHandler, () => { }); } public static void SetProperty<T>( this INotifyPropertyChanged source, Expression<Func<T>> propertyResolver, Expression<Func<T>> fieldResolver, T value, PropertyChangedEventHandler propertyChangedHandler, Action ifChangedCallback) { var property = (PropertyInfo)((MemberExpression)propertyResolver.Body).Member; var field = (FieldInfo)((MemberExpression)fieldResolver.Body).Member; var currentValue = (T)property.GetValue(source, null); if (ReferenceEquals(currentValue, null) && ReferenceEquals(value, null)) return; if (ReferenceEquals(currentValue, value)) return; field.SetValue(source, value); if (propertyChangedHandler != null) propertyChangedHandler(source, new PropertyChangedEventArgs(property.Name)); if (ifChangedCallback != null) ifChangedCallback(); }}public class PersonStaticReflection : INotifyPropertyChanged{ private string _firstName; private string _lastName; public string FirstName { get { return _firstName; } set { this.SetProperty( () => FirstName, () => _firstName, value, this.PropertyChanged); } } public string LastName { get { return _lastName; } set { this.SetProperty( () => LastName, () => _lastName, value, this.PropertyChanged); } } public event PropertyChangedEventHandler PropertyChanged;}internal class SetPropertyExample{ private static void Main() { var person = new PersonStaticReflection(); person.PropertyChanged += (s, e) => Console.WriteLine("PropertyChanged: " + e.PropertyName); person.FirstName = "Mike"; person.LastName = "Strobel"; }}


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: