Clear Filters
Clear Filters

Class property validator reports an error if no default value is set

45 views (last 30 days)
I have the following class defintion which is using a validator function and a custom constructor with an input parser.
classdef MyClass
properties
hWriteFunc(1,1) function_handle
end
methods
function obj = MyClass(varargin)
% Parse input vars
p = inputParser;
addParameter(p,"hWriteFunc",@defaultWriteFunc);
parse(p,varargin{:});
% Set properties
obj.hWriteFunc = p.Results.hWriteFunc;
end
end
end
function defaultWriteFunc(~)
end
When creating an instance of that class by executing
bar = MyClass();
I get the following error:
>> Error defining property 'hWriteFunc' of class 'MyClass'. Unable to construct default object of class function_handle.
Obviously, I can set a default property to avoid the error:
classdef MyClass
properties
hWriteFunc(1,1) function_handle = @defaultWriteFunc;
end
methods
function obj = MyClass(varargin)
% Parse input vars
p = inputParser;
addParameter(p,"hWriteFunc",@defaultWriteFunc);
parse(p,varargin{:});
% Set properties
obj.hWriteFunc = p.Results.hWriteFunc;
end
end
end
function defaultWriteFunc(~)
end
The problem is I now have two points in the class defintion where I need to set the default value (in the properties section and in the custom constructor). I would consider this a bad practise because I am defining the same value on two separate locations and would like to avoid this.
How am I able to define the default value at only one location while still using a validator and the input parser?

Accepted Answer

Michael
Michael on 18 Apr 2024 at 13:46
Edited: Michael on 18 Apr 2024 at 13:48
With all your help I finally find a satisfying solution!
In the end it is pretty easy.
  1. Set default values in the properties section of the class definition
  2. Define the NameValue arguments in the arguments section of the constructor
  3. Only set the property if the NameValue pair is present in the NameValue struct
By doing so you get rid of the need to redundantly define the default value by simultaneously not cluttering the code to much.
classdef ErrorReportHandler < handle
properties
emailAddress string {mustBeTextScalar} = ""
hWriteFunc {mustBeA(hWriteFunc,"function_handle")} = @(x) []
nDebugLogHistory (1,1) double {mustBeFinite} = 10
end
methods
function obj = ErrorReportHandler(NameValueArgs)
% Use Name-Value arguments to set properties
arguments
NameValueArgs.emailAddress
NameValueArgs.hWriteFunc
NameValueArgs.nDebugLogHistory
end
% Set name value pair properties
assignNameValuePair(obj,NameValueArgs,"emailAddress"); % Use this syntax if you have handle class
obj = assignNameValuePair(obj,NameValueArgs,"hWriteFunc"); % Use this syntax if you have a handle class or a value class
obj = assignNameValuePair(obj,NameValueArgs,"nDebugLogHistory");
end
end
end
function obj = assignNameValuePair(obj,NameValueArgs,Name)
if isfield(NameValueArgs,Name)
obj.(Name) = NameValueArgs.(Name);
end
end
PS: If the class is a handle class (like it is here) you can even get rid of the obj = ... part of the property assignment. I just left it there for the others using this on value classes.
Thanks to all of you for your great support on the way to the solution!

More Answers (3)

Aquatris
Aquatris on 17 Apr 2024 at 13:53
Edited: Aquatris on 17 Apr 2024 at 14:12
You can write your own validation function
classdef MyClass
properties
hWriteFunc(1,1) {mustBeFunctionHandle} = @defaultWriteFunc
end
methods
function obj = MyClass(varargin)
if nargin>0
% if user calls the constructor with a function handle
obj.hWriteFunc = varargin{1};
end
end
end
end
function mustBeFunctionHandle(x)
if ~isa(x,'function_handle')
error("Value of Data property must be fHandle")
end
end
  3 Comments
Aquatris
Aquatris on 17 Apr 2024 at 19:42
Edited: Aquatris on 17 Apr 2024 at 19:44
Then I think here is your solution, for more validation function take a look here:
classdef MyClass
properties
hWriteFunc(1,1) {mustBeA(hWriteFunc,'function_handle')} = @defaultWriteFunc
end
methods
function obj = MyClass(varargin)
if nargin>0
% if user calls the constructor with a function handle
obj.hWriteFunc = varargin{1};
end
end
end
end
Michael
Michael on 17 Apr 2024 at 19:57
This comes quite close together with the answer from Matt J. Thank you!

Sign in to comment.


Catalytic
Catalytic on 17 Apr 2024 at 21:27
Edited: Catalytic on 17 Apr 2024 at 21:33
classdef MyClass
properties
WriteFunc
end
methods
function obj = MyClass(varargin)
% Parse input vars
p = inputParser;
addParameter(p,"hWriteFunc",@defaultWriteFunc);
parse(p,varargin{:});
% Set properties
obj.hWriteFunc = p.Results.hWriteFunc;
end
function obj=set.WriteFunc(obj,val)
arguments
obj
val {mustBeA(val,["double","function_handle"])}
end
if isnumeric(val), assert( isempty(val) ,'Must be function handle or []') ;end
obj.WriteFunc=val;
end
end
end
  2 Comments
Michael
Michael on 17 Apr 2024 at 22:18
This is exactly the solution I was looking for. Based on your hint with the set method I also dug a bit deeper into the documentation of Property Get and Set Methods and found this:
  • MATLAB does not call set methods when it assigns default values to the properties during initialization of an object. However, setting property values in the constructor does call set methods.
This is exaclty the behavior I intended.
Michael
Michael on 18 Apr 2024 at 11:30
Edited: Michael on 18 Apr 2024 at 11:33
Below you can find how I implemented your suggestions in case of an error handler (I removed the actual handling function for the sake of shortness here).
I have to admit it is a bit lengthy but at least unambiguous.
Using the set-method prevented the error that occured during the creation of the object.
Is there any shorter solution that allows to achieve the same behavior without having to define all set methods?
classdef ErrorReportHandler < handle
properties
emailAddress % Email address that should be used to send the debug log to in case of an error
emailSubject % Subject of the email template
hWriteFunc % Handle to function that is executed with the exception identifier as a string input argument
nDebugLogHistory % Number of debug log files to keep: -1: unlimited; 0: do not save any debug log files; (int > 0): number of debug log files to keep
end
methods
function obj = ErrorReportHandler(NameValueArgs)
% Use Name-Value arguments to set properties
arguments
NameValueArgs.emailAddress = ""
NameValueArgs.emailSubject = "Debuglog"
NameValueArgs.hWriteFunc = @(x) []
NameValueArgs.nDebugLogHistory = 10
end
% Set properties
obj.emailAddress = NameValueArgs.emailAddress;
obj.emailSubject = NameValueArgs.emailSubject;
obj.hWriteFunc = NameValueArgs.hWriteFunc;
obj.nDebugLogHistory = NameValueArgs.nDebugLogHistory;
end
function set.emailAddress(obj,emailAddress)
arguments
obj
emailAddress string {mustBeValidEmail}
end
obj.emailAddress = emailAddress;
end
function set.emailSubject(obj,emailSubject)
arguments
obj
emailSubject string {mustBeTextScalar}
end
obj.emailSubject = emailSubject;
end
function set.hWriteFunc(obj,hWriteFunc)
arguments
obj
hWriteFunc {mustBeA(hWriteFunc,"function_handle")}
end
obj.hWriteFunc = hWriteFunc;
end
function set.nDebugLogHistory(obj,nDebugLogHistory)
arguments
obj
nDebugLogHistory (1,1) double {mustBeFinite}
end
obj.nDebugLogHistory = nDebugLogHistory;
end
% Removed in this example for the sake of shortness
% reportError(obj,hFigure,ME,data)
end
end
function mustBeValidEmail(emailaddress)
assert(validateemail(emailAddress) || string(emailAddress) == "","Validation:invalidEmailAddress",string(emailAddress) + " is not a valid email address.");
end
% Simplified in this example for the sake of shortness
function valid = validateemail(~)
valid = true;
end
dummyERH = ErrorReportHandler();

Sign in to comment.


Matt J
Matt J on 17 Apr 2024 at 18:57
Edited: Matt J on 17 Apr 2024 at 19:04
I have the following class defintion which is using a validator function
You're not using a property validation function anywhere that I can see. Property validation functions start with "mustBe", e.g., mustBeReal.
You do have a converter to type "function_handle", which I don't think you really need, but if you really want it there, just define any old function handle for the property default, e.g.,
classdef MyClass
properties
hWriteFunc function_handle = @() []
end
end

Categories

Find more on Argument Definitions in Help Center and File Exchange

Tags

Products


Release

R2023a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!