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

8 次查看(过去 30 天)
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?

采纳的回答

Michael
Michael 2024-4-18
编辑:Michael 2024-4-18
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!

更多回答(3 个)

Aquatris
Aquatris 2024-4-17
编辑:Aquatris 2024-4-17
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 个评论
Aquatris
Aquatris 2024-4-17
编辑:Aquatris 2024-4-17
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

请先登录,再进行评论。


Catalytic
Catalytic 2024-4-17
编辑:Catalytic 2024-4-17
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 个评论
Michael
Michael 2024-4-17
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 2024-4-18
编辑:Michael 2024-4-18
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();

请先登录,再进行评论。


Matt J
Matt J 2024-4-17
编辑:Matt J 2024-4-17
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

类别

Help CenterFile Exchange 中查找有关 Properties 的更多信息

标签

产品


版本

R2023a

Community Treasure Hunt

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

Start Hunting!

Translated by