Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
CanExecute WeakRef is cleared when CanExecute-Lambda is in DisplayClass #1192
This one gave me quite some headaches. CanExecute does not work properly depending on where the lambda for it is compiled into. In version 4.5.3 there was no problem. Problems came after upgrade to 5.5.0
Steps to reproduce
CanExecute must execute in all cases. Independent whether the compiler puts it in a DisplayClass for whatever reason or not.
CanExecute lambda callback gets lost after garbage collection when the lambda is compiled into a DisplayClass.
I forced the compiler to use a DisplayClass for the CanExecute lambda by refering a local variable. I checked with ILSpy that this is the case. Once the lambda is put inside a DisplayClass the weak reference will assume that the Target (which is the DisplayClass) is not alive anymore after next GC causing CanExecute to return true in all cases.
Here is my unit test which fails:
Resulting log is:
Yes this happens in release mode too. Have to change the Debug.WriteLine to Console.WriteLine ofcourse.
The thing is I don't really understand yet how the .NET compiler decides to put the lambda expression into a DisplayClass. I have code where I just check () => !HasErrors and it gets into a DisplayClass from time to time even though HasErrors is a member. As soon as that happens CanExecute gives true after the next garbage collection.
I wonder why CTL-1007 was implemented in the first place. The lambda should be alive as long as the command is referenced. So it must be referenced by the command. Not by a weak reference.
As a workaround I use a derived class CatelCommand for now which keeps a reference to the CanExecute func. That passes the unit test above.
CTL-1007 in my opinion was a misunderstanding and at least the changes to Command should be reverted. There can't be CanExecute "listeners", only the CanExecuteChanged event can have listeners.
CanExecuteChanged event should be implemented as weak event pattern as proposed in CTL-1024 not the CanExecute callback.
I think reverting is a big step. I am trying to finish a few deadlines. Maybe we created it to prevent a canexecute in combination with CompositeCommand since that contains a list of commands, but we could argue that it's up to the caller to unsubscribe there.
But if you don't, all items you subscribe to will be kept in memory because they are referenced by the global command (e.g. view models kept in memory, etc).
But then again, if you correctly unsubscribe you won't have that issue.
As it is at the moment Catel commands can't be used. I had all types of issues with CanExecute suddenly returning true in several parts of our application.
To fix the issue all what has to be done is to remove the weak reference to the CanExecute func in Command, making it a simple reference. Lambdas (which should be a quite common usage scenario for CanExecute) are in danger to get cleaned up by accident with the current implementation.
If you don't have the time I can prepare a pull request. The command must reference the lambda or it can get lost.
As reference, in my link about the compiler discussion he even mentioned the potential problem that arised here: