CompositePresentationEvent – MethodAccessException, keep your privates to yourself

This is a known issue with using composite presentation events from Prism in Silverlight.  It’s a side affect of the weak referencing used internally by the event aggregator.  Weak references are used to reduce memory leaks, which is a big plus point of using the event aggregator.  The reason you get this exception with Silverlight (and not WPF) is because there is no private reflection support.  Private reflection is used indirectly because the weak reference taken is to delegate.Target (See DelegateReference).  If the target is a private method, then you won’t be allowed invoke it.  The code below is an example:

private EventAggregator eventAggregator = new EventAggregator();
 
private void ThrowsPrivateAccessException(object sender, RoutedEventArgs e)
{
    eventAggregator.GetEvent<CompositePresentationEvent<string>>().Subscribe(this.PrintNotification);

    Notify("my value");
}
 
private void Notify(string s)
{
    eventAggregator.GetEvent<CompositePresentationEvent<string>>().Publish("my string");
}
 
private void PrintNotification(string s)
{
    Debug.WriteLine(s);
}

Work around is simple, make that method public.  But, I don’t want to make it public.  I want my ViewModel API to be what I want it to be, not what the Event Aggregator needs it to be.  So how do I work around this?

What we need to do is adapt a private method to a public method so it can be invoked:

public class EventSubscriptionAction<T>
{
    private readonly Action<T> subscription;
 
    public EventSubscriptionAction(Action<T> subscription)
    {
        this.subscription = subscription;
    }
 
    public void Target(T arg)
    {
        subscription(arg);
    }
}

Now you can use that class in the subscription.  Note the code below must have a reference to the EventSubscriptionAction.  Remember it’s a weak reference the Event Aggregator is holding, so if you don’t have a reference in your class the target could be null.

private EventSubscriptionAction<string> action;
 
private void Version1(object sender, RoutedEventArgs e)
{
    action = new EventSubscriptionAction<string>(this.PrintNotification);
    eventAggregator.GetEvent<CompositePresentationEvent<string>>().Subscribe(action.Target);
    this.Notify("my value");
}

An interesting side affect of this is you now don’t actually need a method, you can use a lambda (shown below).  This again reduces the friction of subscribing to an event.

private void Version2(object sender, RoutedEventArgs e)
{
    action = new EventSubscriptionAction<string>(s => Debug.WriteLine(s));
    eventAggregator.GetEvent<CompositePresentationEvent<string>>().Subscribe(action.Target);
    this.Notify("my value");
}

But, we can do a bit more.  I don’t want to have to new up a EventSubscriptionAction and I don’t want to have to worry about making sure I keep a reference.  Obviously, I still must keep a reference and tie the lifetime of that reference to the lifetime of the owning class (i.e. the ViewModel), otherwise I’ll create a memory leak.

private List<object> subscriptions = new List<object>();
 
private void Version3(object sender, RoutedEventArgs e)
{
   this.Subscribe(eventAggregator.GetEvent<CompositePresentationEvent<string>>(), s => Debug.WriteLine(s));
    this.Notify("my value");
}
 
public void Subscribe<T>(CompositePresentationEvent<T> @event, Action<T> subscription)
{
    var presnetationSubscription = new EventSubscriptionAction<T>(subscription);
    subscriptions.Add(presnetationSubscription);
    @event.Subscribe(presnetationSubscription.Target);
}

Now what I can do is pull out the Subscribe method and put it in a class which I can reuse.

public class EventSubscriber
{
    private readonly List<object> subscriptions = new List<object>();
 
    public void Subscribe<T>(CompositePresentationEvent<T> @event, Action<T> subscription)
    {
        var presnetationSubscription = new EventSubscriptionAction<T>(subscription);
        subscriptions.Add(presnetationSubscription);
        @event.Subscribe(presnetationSubscription.Target);
    }
}
 
private EventSubscriber eventSubscriber = new EventSubscriber();
 
private void Version4(object sender, RoutedEventArgs e)
{
    eventSubscriber.Subscribe(eventAggregator.GetEvent<CompositePresentationEvent<string>>(), s => Debug.WriteLine(s));
    this.Notify("my value");
}
 

One last step you could do is put it on your base ViewModel  (if all your view models deal with event subscription then it might make sense).

So now you can use the event aggregator and have private methods.  The added bonus is if you want to use it inline without the private method you don’t need to worry about holding a reference each time you do it.

I thought I’d share this as I’ve seen a few times now public methods which really shouldn’t be public.  I’ve also seen a lot of one line privates which could well be expressed better with a lambda.  It’s a simple step to making view model code more readable.  I also thought it was a nice example of how something simple evolves into something useful.  It’s a nice simple example of layering functionality.  Of course, I may well be missing something, so if anyone has an alternative approach please share!

Advertisements

About Tom Peplow

C# .Net developer based in London and the South Coast
This entry was posted in Uncategorized and tagged . Bookmark the permalink.

2 Responses to CompositePresentationEvent – MethodAccessException, keep your privates to yourself

  1. gerichhome says:

    It can be handy to add extension methods for CompositePresentationEvent like this:

    public static class EventSubscriberExtensions
    {
    public static SubscriptionToken Subscribe(this CompositePresentationEvent @event, EventSubscriber subscriber, Action action)
    {
    return Subscribe(@event, subscriber, action, ThreadOption.PublisherThread);
    }

    public static SubscriptionToken Subscribe(this CompositePresentationEvent @event, EventSubscriber subscriber, Action action, bool keepSubscriberReferenceAlive)
    {
    return Subscribe(@event, subscriber, action, ThreadOption.PublisherThread, keepSubscriberReferenceAlive);
    }

    public static SubscriptionToken Subscribe(this CompositePresentationEvent @event, EventSubscriber subscriber, Action action, ThreadOption threadOption)
    {
    return Subscribe(@event, subscriber, action, threadOption, false);
    }

    public static SubscriptionToken Subscribe(this CompositePresentationEvent @event, EventSubscriber subscriber, Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive)
    {
    return Subscribe(@event, subscriber, action, threadOption, keepSubscriberReferenceAlive, null);
    }

    public static SubscriptionToken Subscribe(this CompositePresentationEvent @event, EventSubscriber subscriber, Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate filter)
    {
    return subscriber.Subscribe(@event, action, threadOption, keepSubscriberReferenceAlive, filter);
    }
    }

    public class EventSubscriber
    {
    protected readonly List subscriptions = new List();

    internal virtual SubscriptionToken Subscribe(CompositePresentationEvent @event, Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate filter)
    {
    var subscription = new EventSubscriptionAction(action);
    subscriptions.Add(subscription);
    return @event.Subscribe(subscription.Target, threadOption, keepSubscriberReferenceAlive, filter);
    }
    }

    so in your code it will look almost like standart subscribe:

    private EventSubscriber eventSubscriber = new EventSubscriber();

    private void Version5(object sender, RoutedEventArgs e)
    {
    eventAggregator.GetEvent<CompositePresentationEvent>().Subscribe(eventSubscriber, s => Debug.WriteLine(s));
    this.Notify(“my value”);
    }

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