views:

511

answers:

5

Hi,

I have an event that I am subscribing to in a View Model. The event subscription is done in the constructor of the view model which is created via unity.

What I found is if I subscribe as:

showViewAEvent.Subscribe(ShowViewAHasBeenRequested) or showViewAEvent.Subscribe(ShowViewAHasBeenRequested, False) I get the following error:

    // {System.MethodAccessException: ModuleA.Views.ModuleAViewModel.ShowViewAHasBeenRequested(Boolean)
    //at System.Delegate.BindToMethodInfo(Object target, RuntimeMethodHandle method, RuntimeTypeHandle methodType, DelegateBindingFlags flags)
    //at System.Delegate.CreateDelegate(Type type, Object firstArgument, MethodInfo method, Boolean throwOnBindFailure)
    //at System.Delegate.CreateDelegate(Type type, Object firstArgument, MethodInfo method)
    //at Microsoft.Practices.Composite.Events.DelegateReference.TryGetDelegate()
    //at Microsoft.Practices.Composite.Events.DelegateReference.get_Target()
    //at Microsoft.Practices.Composite.Events.EventSubscription`1..ctor(IDelegateReference actionReference, IDelegateReference filterReference)
    //at Microsoft.Practices.Composite.Presentation.Events.CompositePresentationEvent`1.Subscribe(Action`1 action, ThreadOption threadOption, Boolean keepSubscriberReferenceAlive, Predicate`1 filter)
    //at Microsoft.Practices.Composite.Presentation.Events.CompositePresentationEvent`1.Subscribe(Action`1 action, ThreadOption threadOption, Boolean keepSubscriberReferenceAlive)
    //at Microsoft.Practices.Composite.Presentation.Events.CompositePresentationEvent`1.Subscribe(Action`1 action, Boolean keepSubscriberReferenceAlive)
    //at ModuleA.Views.ModuleAViewModel..ctor(IEventAggregator eventAggregator, IRegionManager regionManager)
    //at BuildUp_ModuleA.Views.ModuleAViewModel(IBuilderContext )
    //at Microsoft.Practices.ObjectBuilder2.DynamicMethodBuildPlan.BuildUp(IBuilderContext context)
    //at Microsoft.Practices.ObjectBuilder2.BuildPlanStrategy.PreBuildUp(IBuilderContext context)
    //at Microsoft.Practices.ObjectBuilder2.StrategyChain.ExecuteBuildUp(IBuilderContext context)}

But, if I set the flag to true on the event subscription, I do not get the error.

As I am new to prism, I am still trying to work out if I am creating the subscription in the right place.

JD.

A: 

Upon further research I found this thread: http://compositewpf.codeplex.com/Thread/View.aspx?ThreadId=57362

I didn't realize that the Subscribe call was actually in the CallStack or I would have realized this earlier. Here's an excerpt:

Silverlight does not support weak references to lambda expressions or anonymous delegates. Therefore, the filter parameter must be a separate method if you are targeting Silverlight.

Are you trying to use a lambda as your handler for that subscription? If so, it looks like all you need to do is use a real method.

EventService.GetEvent<GenericEvent<string>>().Subscribe(YourAction)

.....

public void YourAction(string topic)
{
   if(topic == "something")
   {
      // more code
   }
}
Anderson Imes
I've been bitten by this problem as well, but I don't think it is the answer to the question, since his subscription works when he passes true to the subscribe method.
Bryan Batchelder
Doh, I made the same mistake as you originally did. If in fact the Subscribe() is failing, then I am not sure what is up. The code shown in the question is not using a lambda. Maybe Konamiman is right. I thought the event was working OK if he passed in true, which would eliminate it being a non-public method...
Bryan Batchelder
Konamiman may indeed be correct about this. I'd be interested to hear back from the OP, but he/she has a bad accept rate, so I'm not sure if we'll hear back.
Anderson Imes
Sorry, I will try to get my accept rate up. Yes, it is a public method and it works when I pass true.
JD
A: 

Is the ShowViewAHasBeenRequested method public? If not, it will not be reachable by the invoking code.

Konamiman
This is definitely a good thing to remember about events in Prism, but I don't think it is the answer to the problem since the event works if he passes True to the Subscribe method.
Bryan Batchelder
A: 

Has your ViewModel gone out of scope when the event is published? Passing True in the Subscribe method creates a strong reference, which keeps the Subscriber from getting GC'd after it has gone out of scope. Understand that doing this will create a memory leak, as every ViewModel you instanciate will continue to live on and respond to those published events.

I ran into this same problem when setting up some Subscriptions to events inside my Module's Initialize method - as far as I could tell nothing was holding a reference to my Module after it was done setting everything up.

Bryan Batchelder
How can I check if my viewModel has gone out of scope?
JD
+2  A: 

This is a known issue fully documented here :

http://compositewpf.codeplex.com/WorkItem/View.aspx?WorkItemId=4925

Bug in CompositePresentationEvent<>.Subscribe() prevents weak event references Title is required

Description Description is required OVERVIEW:

The Subscribe() method of this class is documented as creating WeakReferences by default or when specified as keepSubscriberReferenceAlive=false in the overloads that include that parameter.

DETAILS:

This behavior is only correctly observed when a filter delegate is supplied. In all other cases (and all overloads of the Subscribe() method), a strong reference is created - regardless of the documented default and regardless of any supplied value for the keepSubscriberReferenceAlive parameter.

The source of this bug can be found in the following overload of this method:

CompositePresentationEvent.Subscribe(Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate filter)

In this method, the "filter" parameter is inspected. If the filter is not null, then processing continues correctly. However, if this parameter is null then a new pass-through delegate (always returns true) is created and used for the filter. The bug is that the DelegateReference object that is created from this pass-through delegate has the keepReferenceAlive parameter hard-coded to the value "true". This value should not be hard-coded, and instead the incoming parameter keepSubscriberReferenceAlive should be passed.

WORKAROUND:

There is a simple workaround for this issue. When registering a subscription, you should always use the verbose overload stated above, and always supply a filter delegate. Never pass "null" for the filter parameter. If the subscription should not be filtered, then a pass-through filter delegate should be used when a weak event reference is desired (the typical scenario):

EventAggregator.GetEvent().Subscribe(MyHandler, ThreadOption.PublisherThread, false, (dummy) => true);

There is NO workaround for the following abbreviated overloads, and these should not be used until the underlying bug has been patched:

CompositePresentationEvent.Subscribe(Action action) CompositePresentationEvent.Subscribe(Action action, ThreadOption threadOption) CompositePresentationEvent.Subscribe(Action action, bool keepSubscriberReferenceAlive) CompositePresentationEvent.Subscribe(Action action, ThreadOption threadOption, bool keepSubscriberReferenceAlive)

A: 

I had exactly the same problem and solved it by making both the filter method and action method public as per user188067 and Konamiman's responses. i.e.

showViewAEvent.Subscribe(ShowViewAHasBeenRequested, ThreadOption.UIThread, false, ShouldHandleEvent);

public bool ShouldHandleError(object obj)
{
    return true;
}

public void ShowViewAHasBeenRequested(object obj)
{
...
}
Tana