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

5 vues (au cours des 30 derniers jours)
Michael
Michael le 17 Avr 2024
Modifié(e) : Michael le 18 Avr 2024
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?

Réponse acceptée

Michael
Michael le 18 Avr 2024
Modifié(e) : Michael le 18 Avr 2024
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!

Plus de réponses (3)

Aquatris
Aquatris le 17 Avr 2024
Modifié(e) : Aquatris le 17 Avr 2024
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 commentaires
Aquatris
Aquatris le 17 Avr 2024
Modifié(e) : Aquatris le 17 Avr 2024
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 le 17 Avr 2024
This comes quite close together with the answer from Matt J. Thank you!

Connectez-vous pour commenter.


Catalytic
Catalytic le 17 Avr 2024
Modifié(e) : Catalytic le 17 Avr 2024
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 commentaires
Michael
Michael le 17 Avr 2024
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 le 18 Avr 2024
Modifié(e) : Michael le 18 Avr 2024
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();

Connectez-vous pour commenter.


Matt J
Matt J le 17 Avr 2024
Modifié(e) : Matt J le 17 Avr 2024
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

Catégories

En savoir plus sur Properties dans Help Center et File Exchange

Tags

Produits


Version

R2023a

Community Treasure Hunt

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

Start Hunting!

Translated by