I was asked how to get Mono.Cecil to rewrite PDB files so that VS can step through source when debugging.  Here’s an excerpt of the code I’m using to do this.  It works fine for me!

var assemblyResolver = new DefaultAssemblyResolver();
var assemblyLocation = Path.GetDirectoryName(AssemblyPath);
assemblyResolver.AddSearchDirectory(assemblyLocation);
if (!string.IsNullOrEmpty(HintPath))
{
assemblyResolver.AddSearchDirectory(HintPath);
}
var silverlightAssemblyPath = Environment.ExpandEnvironmentVariables(@”%ProgramFiles%\Reference Assemblies\Microsoft\Framework\Silverlight\v4.0\”);
assemblyResolver.AddSearchDirectory(silverlightAssemblyPath);
var readerParameters = new ReaderParameters { AssemblyResolver = assemblyResolver };
var writerParameters = new WriterParameters();
var pdbName = Path.ChangeExtension(AssemblyPath, “pdb”);
if (File.Exists(pdbName))
{
var symbolReaderProvider = new PdbReaderProvider();
readerParameters.SymbolReaderProvider = symbolReaderProvider;
readerParameters.ReadSymbols = true;
writerParameters.WriteSymbols = true;
}
var assemblyDefinition = AssemblyDefinition.ReadAssembly(AssemblyPath, readerParameters);
var weaver = new NotifyPropertyChangedWeaver();
weaver.Weave(assemblyDefinition);
assemblyDefinition.Write(AssemblyPath, writerParameters);

 

I just wrote a base class that I’m going to derive all of my notifying Domain Models from. It solves the horror of passing around the property name of a changing property in the INotifyPropertyChanged implementation. This has the enormous benefit of being able to check your Property Names at compile-time and also allows you to keep everything in sync when using tools like Resharper (if you don’t use Resharper – then you really should!) to rename things. It uses lambda expressions to specify the Property that’s changing.

    public abstract class ModelBase<T> : INotifyPropertyChanged where T : ModelBase<T>
    {
        #region Events
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion

        #region Public Methods
        public static string GetPropertyName<R>(Expression<Func<T, R>> expression)
        {
            var memberExpression = expression.Body as MemberExpression;
            if (memberExpression == null)
            {
                throw new ArgumentException("'expression' should be a member expression");
            }
            var propertyName = memberExpression.Member.Name;
            return propertyName;
        }
        #endregion

        #region Protected Methods
        protected void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        
        protected void RaisePropertyChanged<R>(Expression<Func<T, R>> expression)
        {
            var propertyName = GetPropertyName(expression);
            this.OnPropertyChanged(propertyName);
        }
        #endregion
}

Following on from my woes about ObservableCollection<T>, and my annoyance at having to maintain a parallel collection to ensure all INotifyPropertyChanged and INotifyCollectionChanged subscriptions are removed, I decided to look down the avenue of Weak event subscriptions.  To that end I can up with the classes WeakPropertyChangedListner and WeakCollectionChangedListener.  The code for these is as follows:

 

    public class WeakPropertyChangedListener
    {
        #region Private Fields
        private INotifyPropertyChanged _source;
        private WeakReference _listener;
        #endregion

        #region Ctor
        public WeakPropertyChangedListener(INotifyPropertyChanged source, PropertyChangedEventHandler listener)
        {
            this._source = source;
            this._source.PropertyChanged += this.OnPropertyChanged;
            this._listener = new WeakReference(listener);
        }
        #endregion

        #region Public Methods
        public static WeakPropertyChangedListener Create(INotifyPropertyChanged source, PropertyChangedEventHandler listener)
        {
            return new WeakPropertyChangedListener(source, listener);
        }

        public void Disconnect()
        {
            if (_source != null)
            {
                _source.PropertyChanged -= this.OnPropertyChanged;
                _source = null;
                _listener = null;
            }
        }
        #endregion

        #region Private Methods
        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (_listener != null)
            {
                var handler = _listener.Target as PropertyChangedEventHandler;
                if (handler != null)
                {
                    handler(sender, e);
                }
                else
                {
                    this.Disconnect();
                }
            }
        }
        #endregion
    }

and:

    public class WeakCollectionChangedListener
    {
        #region Private Fields
        private INotifyCollectionChanged _source;
        private WeakReference _handler;
        #endregion

        #region Ctor
        public WeakCollectionChangedListener(INotifyCollectionChanged source, NotifyCollectionChangedEventHandler handler)
        {
            this._source = source;
            this._source.CollectionChanged += this.OnCollectionChanged;
            this._handler = new WeakReference(handler);
        }
        #endregion

        #region Public Methods
        public static WeakCollectionChangedListener Create(INotifyCollectionChanged source, NotifyCollectionChangedEventHandler handler)
        {
            return new WeakCollectionChangedListener(source, handler);
        }

        public void Disconnect()
        {
            if (_source != null)
            {
                _source.CollectionChanged -= this.OnCollectionChanged;
                _source = null;
                _handler = null;
            }
        }
        #endregion

        #region Private Methods
        private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (_handler != null)
            {
                var handler = _handler.Target as NotifyCollectionChangedEventHandler;
                if (handler != null)
                {
                    handler(sender, e);
                }
                else
                {
                    this.Disconnect();
                }
            }
        }
        #endregion
    }

 Then you simple subscribe like this:

WeakPropertyChangedListener.Create(person, OnPersonPropertyChanged);

WeakCollectionChangedListener.Create(person.Children, OnPersonCollectionChanged);

Safe in the knowledge that your observing the notifications will not cause your domain objects to hang around.

<rant>

Thought I’d share with you some of my gripes with the ObservableCollection<T> class that forms the basis of just about all data-bindable domain models in WPF and Silverlight.

Firstly why does it not support an AddRange(IEnumerable<T> items) method like other collections?

The EventHandler signature looks like:

private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)

and NotifyCollectionChangedEventArgs has a property NewItems of type IList.  This implies that you should be able to add multiple items, but the API doesn’t support it.  Infuriating!

More importantly, when you Clear an ObservableCollection you get called on the CollectionChanged event with NotifyCollectionChangedAction.Reset.  However when you look at e.OldItems you’d expect to find the list of items that were cleared, right?  Sadly not.  I often find that I hook into various event handlers in the OnCollectionChanged method.  In order to clear these subscriptions on reset it means that I have to hold a parallel collection of the items in the ObservableCollection.  Not good.

Also, why is there not an ObservableDictionary<TKey, TValue>?  Various people have attempted to implement this, but it should really be part of the framework and understood by data binding.

I’m hopeful that these issues will go away with the BCL changes for the very groovy System.Reactive framework.

</rant>