2

I'm using the code from Weak Events in .Net, the easy way to handle monitoring changes to an observable collection. The code has worked without any problems for months. I recently updated to a new computer. After getting everything setup and pulling down the code from my repository I encountered a strange problem. The code no longer works!

Here's the relevant portion of my code, it all takes place in the constructor:

 public class PurchaseOrderReceipt : BaseEntity
{
    /// <summary>
    ///     Initializes a new instance of the <see cref="PurchaseOrderReceipt" /> class.
    /// </summary>
    public PurchaseOrderReceipt()
    {
        this.ReceiptItems = new ObservableCollection<PurchaseOrderItemReceipt>();
        this.DateReceived = DateTime.Now;

        this.ReceiptItems.ObserveCollectionChanged()
            .SubscribeWeakly(this, (target, eventArgs) => target.ReceiptItemsChanged());
    }

The exception is thrown on the SubscribeWeakly line with the following error message: ArgumentException: onNext must refer to a static method, or else the subscription will still hold a strong reference to target

I can recreate the problem in LinqPad just by creating an instance of the PurchaseOrderReceipt.

Odder still if I write a simple class in LinqPad that mirrors the setup in the PurchaseOrderReceipt class than it works.

LinqPad code:

void Main()
{
    var x = new Test(); 
    x.ReceiptItems.Add(new PurchaseOrderItemReceipt());

}

public class Test:BaseEntity
{
    public ObservableCollection<PurchaseOrderItemReceipt> ReceiptItems {get; set;}

    public Test()
    {
        this.ReceiptItems = new ObservableCollection<PurchaseOrderItemReceipt>();
        this.ReceiptItems.ObserveCollectionChanged().SubscribeWeakly(this,(target, eventargs) => target.TestChanged());
    }

    private void TestChanged()
    {
        "Changed!".Dump();
    }
}

Changed! is printed out in the results window.

Here's the CustomReactiveExtension class from the link at the top.

public static class CustomReactiveExtension
    {
        public static IObservable<EventPattern<NotifyCollectionChangedEventArgs>> ObserveCollectionChanged(this INotifyCollectionChanged collection)
        {
            return Observable.FromEventPattern<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(
                handler => (sender, e) => handler(sender, e),
                handler => collection.CollectionChanged += handler,
                handler => collection.CollectionChanged -= handler);
        }

        public static IDisposable SubscribeWeakly<T, TTarget>(this IObservable<T> observable, TTarget target, Action<TTarget, T> onNext) where TTarget : class
        {
            var reference = new WeakReference(target);

            if (onNext.Target != null)
            {
                throw new ArgumentException("onNext must refer to a static method, or else the subscription will still hold a strong reference to target");
            }

            IDisposable subscription = null;
            subscription = observable.Subscribe(item =>
            {
                var currentTarget = reference.Target as TTarget;
                if (currentTarget != null)
                {
                    onNext(currentTarget, item);
                }
                else
                {
                    subscription.Dispose();
                }
            });

            return subscription;
        }
    }

Any ideas?

Jason Massey
  • 1,088
  • 10
  • 18

1 Answers1

1

I am not 100% certain but my guess is that either different versions of the compiler or different compilation options are resulting in your lambda being compiled to an instance method rather than a static method.

The easiest solution to this would be to explicitly implement a static method to be used as your onNext callback, i.e:

private static void OnReceiptItemsChanged(PurchaseOrderReceipt target, 
    EventPattern<NotifyCollectionChangedEventArgs> eventPattern)
{
    // TODO Do something here
}

And then use SubscribeWeakly like so:

this.ReceiptItems.ObserveCollectionChanged().SubscribeWeakly(this, OnReceiptItemsChanged);

Now regardless of which compiler you use or which compilation options the callback is always a static method.

Lukazoid
  • 19,016
  • 3
  • 62
  • 85