MATLAB: How To Avoid Triggering Property Setter Method

classMATLABoopset method

We are using set methods in a number of our handle derived classes to do extended property validations.
The general requirement of what we did before was:
  1. Check that the new value is OK
  2. Backup the old value
  3. Set the new value
  4. Run a series of calculations
  5. If an error occurrs during calculations, restore the old value
Previously we were using dynamic properties for this which allowed for a completely custom set function to be used. We are however now trying to switch to non-dynamic properties due to the performance penalties of dynamic prop lookups.
In order to acheive similar behaviour, we need to use the set.PropName function for each of the non-dynamic properties. Ideally this set function would just call our original set method directory:
function set.PropName(obj, val)
obj.customSetter('PropName',val);
end
However this according to the help text won't work because if we set the property value in customSetter, then "property assignments made from functions called by a set method do call the set method" which would surely cause an infinite loop, and seems a rather strange design "feature".
Instead we end up with this kind of structure:
function set.PropName(obj, val)
[doc, val] = obj.setFuncPre('PropName', val);
oldVal = obj.PropName;
obj.PropName = val;
me = obj.setFuncPost('PropName', doc);
if ~isempty(me)
obj.PropName = oldVal;
obj.setFuncRestored('PropName', me);
end
end
Which becomes incredibly long winded when we have hundreds of properties across multiple classes which all have to do the same thing.
So the question is really, is there a way to bypass calling the setter function so that we can use the first example? Or is there a better approach to this?

Best Answer

Hi Thomas -
This is an interesting problem. If I understand correctly, you need to have the property set because you have some validation that is going to depend on the 'new' value being stored in the property (i.e. the post-set calculations that might throw an error).
I can think a few strategies, but none are perfect. In terms of 'is there a better approach' I suppose it would be ideal if you could validate the new values without needing to 'commit' the set, but I can easily see why that might be challenging for a really complex object/calculation.
I can't think of a way to prevent the setter from recursing when using a separate method - I tried some tricks like anonymous functions and evalin to no avail. Maybe you could use a logical 'lock' property to achieve the same effect? It's not a great solution but it would clean up the code a little bit (example below).
Some alternate strategies include making a public and a private version of each property to add another 'layer' (this probably adds just as much complexity) or to make use of PreSet and PostSet listeners (however this means storing the old value somewhere, so I think not great).
The following code keeps the line that does the actual setting in the setter. It prevents recursion by using the customSetter when called directly (obj.setlock is false) but a direct set when called from customSetter (obj.setlock is true). I think you can use one customSetter for multiple properties (you'd need to pass the property name as an argument), but whether or not you can achieve one 'lock' property depends on whether setting a property can have a side effect on another property.
Note that I used try/catch to trap the errors, but your me = ... line would work just as well here if that's how you track your exceptions. However it is critical is that the setlock property is set to false by the end of customSetter no matter what happens. Also note - I didn't handle the potential conflict around saving and loading objects in the class - I think that's okay because the object can't be saved with prop in an invalid state, so it doesn't matter which branch the setter goes down (I think it will always do the customSetter branch).
classdef foo_set < handle
properties
prop
end
properties (Transient, Access=private)
setlock=false;
end
methods
function set.prop(obj,val)
if ~obj.setlock
obj.customSetter(val);
else
obj.prop=val;
end
end
function customSetter(obj,val)
% 1. Do pre-validation here using val
oldval = obj.prop; % 2. Backup the old value
obj.setlock=true; % A. lock setter to prevent recursion
obj.prop=val; % 3. Set the new value
try
% 4. Run post-set validations here
catch
% 5. Error occurred, restore old value
obj.prop=oldval;
end
% B. Whether or not error occurred, unlock the setter
obj.setlock=false;
end
end
end