子类构造函数拒绝接受任何名称-值参数
Subclass constructor refuses to accept any name-value arguments
TL;DR
我在使用 arguments
功能时遇到问题,如果将 any 名称-值参数对传递给子类,则子类无法实例化。
(Class定义在最后。)
arguments
functionality, introduced in R2019b, brings great promise in terms of simplifying argument validation and removing boilerplate code from functions [1]. However, upon trying to implement name-value (NV) arguments taken from public
class properties,我得到以下错误:
Invalid argument list. Check for wrong number of positional arguments or placement of positional arguments after
name-value pairs. Also, check for name-value pairs with invalid names or not specified in pairs.
甚至在执行任何子类代码之前。此消息令人困惑,因为制表符补全似乎对 NV 对按预期工作:
此外,如果根本不传递参数,一切正常:
>> FluidLayer()
ans =
FluidLayer with properties:
prop1: NaN
prop2: NaN
对比
>> FluidLayer('prop1',1)
Error using FluidLayer
Invalid argument list. ...
问题:
- 为什么我会收到错误消息?我认为我没有以某种无意或未记录的方式使用
arguments
机制,所以我对我可能做错了什么感到双重困惑(假设这不是错误)。
- 除了放弃整个
arguments
方法(我想保留参数名称建议)之外,还能做些什么来解决这个问题?我考虑过使用 functionSignatures.json
approach 过渡到 varargin
and/or - 但这些需要更多的工作。
classdef Layer < handle & matlab.mixin.Heterogeneous
properties (GetAccess = public, SetAccess = protected)
prop1(1,1) double {mustBeNonempty} = NaN
prop2(1,1) double {mustBeNonempty} = NaN
end % properties
methods (Access = protected)
function layerObj = Layer(props)
% A protected/private constructor means this class cannot be instantiated
% externally, but only through a subclass.
arguments
props.?Layer
end
% Copy field contents into object properties
fn = fieldnames(props);
for idxF = 1:numel(fn)
layerObj.(fn{idxF}) = props.(fn{idxF});
end
end % constructor
end % methods
end
classdef FluidLayer < Layer
properties (GetAccess = public, SetAccess = protected)
% Subclass-specific properties
end % properties
methods
function layerObj = FluidLayer(props)
arguments
props.?FluidLayer
end
% Create superclass:
propsKV = namedargs2cell(props);
layerObj = layerObj@Layer(propsKV{:});
% Custom modifications:
end % constructor
end % methods
end
我已经能够将您的示例简化为:
classdef Layer
properties (GetAccess = public, SetAccess = protected)
prop1
prop2
end % properties
methods
function layerObj = Layer(props)
arguments
props.?Layer
end
disp(props)
end % constructor
end % methods
end
现在 Layer('prop1',1)
按照您的描述抛出错误。
因此,它与subclassing 或继承无关。
但是,如果我们删除 SetAccess = protected
限制(保留具有 public get 和 set 访问权限的属性),那么一切都会如您所愿。
我不知道为什么限制设置访问会限制这个用例,因为它与编写这些属性无关,而且 class 方法无论如何都应该设置访问。我的猜测是这是一个错误。
R2020a 的文档指出 structName.?ClassName
语法只能用于“ 由 class 定义的可设置属性(即所有具有 [=14= 的属性) ] SetAccess
)”。因此,明确不支持将其与 protected
属性一起使用。
因此,如果我们要使用"automatic" arguments
验证机制,我们别无选择,只能将SetAccess
设置为public
。但是,此解决方案会将对象属性暴露给不需要的外部更改,因此建议根据以下几个原则采取解决方法:
根据文档的要求,properties
现在有 public
SetAccess
。
- 添加了自定义 setter,可根据
dbstack
和 meta.class
比较执行访问验证。
新 Layer.m
(注意 2 个新的 methods
块):
classdef Layer < handle & matlab.mixin.Heterogeneous
properties (GetAccess = public, SetAccess = public)
prop1(1,1) double {mustBeNonempty} = NaN
prop2(1,1) double {mustBeNonempty} = NaN
end % properties
%% Constructor
methods (Access = protected)
function layerObj = Layer(props)
% A protected/private constructor means this class cannot be instantiated
% externally, but only through a subclass.
arguments
props.?Layer
end
% Copy field contents into object properties
fn = fieldnames(props);
for idxF = 1:numel(fn)
layerObj.(fn{idxF}) = props.(fn{idxF});
end
end % constructor
end % protected methods
%% Setters & Getters
methods
function set.prop1(obj, val)
if Layer.getCallerMetaclass() <= ?Layer
obj.prop1 = val;
else
Layer.throwUnprotectedAccess();
end
end
function set.prop2(obj, val)
if Layer.getCallerMetaclass() <= ?Layer
obj.prop2 = val;
else
Layer.throwUnprotectedAccess();
end
end
end % no-attribute methods
%% Pseudo-protected implementation
methods (Access = protected, Static = true)
function throwUnprotectedAccess()
stack = dbstack(1);
[~,setterName,~] = fileparts(stack(1).name);
throw(MException('Layer:unprotectedPropertyAccess',...
['Unable to set "', stack(1).name(numel(setterName)+2:end),...
'", as it is a protected property!']));
end
function mc = getCallerMetaclass()
stack = dbstack(2, '-completenames');
if isempty(stack)
mc = ?meta.class;
else
[~,className,~] = fileparts(stack(1).file);
mc = meta.class.fromName(className);
end
end
end % protected static methods
end % classdef
新FluidLayer.m
(添加了foo
方法):
classdef FluidLayer < Layer
properties (GetAccess = public, SetAccess = protected)
% Subclass-specific properties
end % properties
methods
%% Constructor
function layerObj = FluidLayer(props)
arguments
props.?Layer
end
% Create superclass:
propsKV = namedargs2cell(props);
layerObj = layerObj@Layer(propsKV{:});
end % constructor
function obj = foo(obj)
obj.prop1 = obj.prop1 + 1;
end
end % methods
end % classdef
下面是其工作原理的演示:
>> fl = FluidLayer('prop1', 2, 'prop2', 1)
fl =
FluidLayer with properties:
prop1: 2
prop2: 1
>> fl.prop1 = 5; % attempting to set property directly (unintended)
Error using Layer/throwUnprotectedAccess (line 51)
Unable to set "prop1", as it is a protected property!
Error in Layer/set.prop1 (line 32)
Layer.throwUnprotectedAccess();
>> fl.foo() % attempting to set property through method (intended)
ans =
FluidLayer with properties:
prop1: 3
prop2: 1
>>
结论:使用setter方法可以克服SetAccess = public
限制,但需要对class代码进行复杂的修改。
实用笔记:
getCallerMetaclass
的局限性在于它无法正确识别包 - 导致可能出现空的 metaclass 对象。所以请记住,如果是这种情况,应该修改此功能。
dbstack
被多次调用,虽然没有必要(它可以在 setter 中调用一次,然后可以传递结果)。
- 不同属性的 setter 代码有 5 行长,大部分都是重复的(属性 名称除外)- 这可以通过抓取 属性 名称来改进通过
dbstack
.
TL;DR
我在使用 arguments
功能时遇到问题,如果将 any 名称-值参数对传递给子类,则子类无法实例化。
(Class定义在最后。)
arguments
functionality, introduced in R2019b, brings great promise in terms of simplifying argument validation and removing boilerplate code from functions [1]. However, upon trying to implement name-value (NV) arguments taken from public
class properties,我得到以下错误:
Invalid argument list. Check for wrong number of positional arguments or placement of positional arguments after
name-value pairs. Also, check for name-value pairs with invalid names or not specified in pairs.
甚至在执行任何子类代码之前。此消息令人困惑,因为制表符补全似乎对 NV 对按预期工作:
此外,如果根本不传递参数,一切正常:
>> FluidLayer()
ans =
FluidLayer with properties:
prop1: NaN
prop2: NaN
对比
>> FluidLayer('prop1',1)
Error using FluidLayer
Invalid argument list. ...
问题:
- 为什么我会收到错误消息?我认为我没有以某种无意或未记录的方式使用
arguments
机制,所以我对我可能做错了什么感到双重困惑(假设这不是错误)。 - 除了放弃整个
arguments
方法(我想保留参数名称建议)之外,还能做些什么来解决这个问题?我考虑过使用functionSignatures.json
approach 过渡到varargin
and/or - 但这些需要更多的工作。
classdef Layer < handle & matlab.mixin.Heterogeneous
properties (GetAccess = public, SetAccess = protected)
prop1(1,1) double {mustBeNonempty} = NaN
prop2(1,1) double {mustBeNonempty} = NaN
end % properties
methods (Access = protected)
function layerObj = Layer(props)
% A protected/private constructor means this class cannot be instantiated
% externally, but only through a subclass.
arguments
props.?Layer
end
% Copy field contents into object properties
fn = fieldnames(props);
for idxF = 1:numel(fn)
layerObj.(fn{idxF}) = props.(fn{idxF});
end
end % constructor
end % methods
end
classdef FluidLayer < Layer
properties (GetAccess = public, SetAccess = protected)
% Subclass-specific properties
end % properties
methods
function layerObj = FluidLayer(props)
arguments
props.?FluidLayer
end
% Create superclass:
propsKV = namedargs2cell(props);
layerObj = layerObj@Layer(propsKV{:});
% Custom modifications:
end % constructor
end % methods
end
我已经能够将您的示例简化为:
classdef Layer
properties (GetAccess = public, SetAccess = protected)
prop1
prop2
end % properties
methods
function layerObj = Layer(props)
arguments
props.?Layer
end
disp(props)
end % constructor
end % methods
end
现在 Layer('prop1',1)
按照您的描述抛出错误。
因此,它与subclassing 或继承无关。
但是,如果我们删除 SetAccess = protected
限制(保留具有 public get 和 set 访问权限的属性),那么一切都会如您所愿。
我不知道为什么限制设置访问会限制这个用例,因为它与编写这些属性无关,而且 class 方法无论如何都应该设置访问。我的猜测是这是一个错误。
R2020a 的文档指出 structName.?ClassName
语法只能用于“ 由 class 定义的可设置属性(即所有具有 [=14= 的属性) ] SetAccess
)”。因此,明确不支持将其与 protected
属性一起使用。
因此,如果我们要使用"automatic" arguments
验证机制,我们别无选择,只能将SetAccess
设置为public
。但是,此解决方案会将对象属性暴露给不需要的外部更改,因此建议根据以下几个原则采取解决方法:
-
根据文档的要求,
properties
现在有public
SetAccess
。- 添加了自定义 setter,可根据
dbstack
和meta.class
比较执行访问验证。
新 Layer.m
(注意 2 个新的 methods
块):
classdef Layer < handle & matlab.mixin.Heterogeneous
properties (GetAccess = public, SetAccess = public)
prop1(1,1) double {mustBeNonempty} = NaN
prop2(1,1) double {mustBeNonempty} = NaN
end % properties
%% Constructor
methods (Access = protected)
function layerObj = Layer(props)
% A protected/private constructor means this class cannot be instantiated
% externally, but only through a subclass.
arguments
props.?Layer
end
% Copy field contents into object properties
fn = fieldnames(props);
for idxF = 1:numel(fn)
layerObj.(fn{idxF}) = props.(fn{idxF});
end
end % constructor
end % protected methods
%% Setters & Getters
methods
function set.prop1(obj, val)
if Layer.getCallerMetaclass() <= ?Layer
obj.prop1 = val;
else
Layer.throwUnprotectedAccess();
end
end
function set.prop2(obj, val)
if Layer.getCallerMetaclass() <= ?Layer
obj.prop2 = val;
else
Layer.throwUnprotectedAccess();
end
end
end % no-attribute methods
%% Pseudo-protected implementation
methods (Access = protected, Static = true)
function throwUnprotectedAccess()
stack = dbstack(1);
[~,setterName,~] = fileparts(stack(1).name);
throw(MException('Layer:unprotectedPropertyAccess',...
['Unable to set "', stack(1).name(numel(setterName)+2:end),...
'", as it is a protected property!']));
end
function mc = getCallerMetaclass()
stack = dbstack(2, '-completenames');
if isempty(stack)
mc = ?meta.class;
else
[~,className,~] = fileparts(stack(1).file);
mc = meta.class.fromName(className);
end
end
end % protected static methods
end % classdef
新FluidLayer.m
(添加了foo
方法):
classdef FluidLayer < Layer
properties (GetAccess = public, SetAccess = protected)
% Subclass-specific properties
end % properties
methods
%% Constructor
function layerObj = FluidLayer(props)
arguments
props.?Layer
end
% Create superclass:
propsKV = namedargs2cell(props);
layerObj = layerObj@Layer(propsKV{:});
end % constructor
function obj = foo(obj)
obj.prop1 = obj.prop1 + 1;
end
end % methods
end % classdef
下面是其工作原理的演示:
>> fl = FluidLayer('prop1', 2, 'prop2', 1)
fl =
FluidLayer with properties:
prop1: 2
prop2: 1
>> fl.prop1 = 5; % attempting to set property directly (unintended)
Error using Layer/throwUnprotectedAccess (line 51)
Unable to set "prop1", as it is a protected property!
Error in Layer/set.prop1 (line 32)
Layer.throwUnprotectedAccess();
>> fl.foo() % attempting to set property through method (intended)
ans =
FluidLayer with properties:
prop1: 3
prop2: 1
>>
结论:使用setter方法可以克服SetAccess = public
限制,但需要对class代码进行复杂的修改。
实用笔记:
getCallerMetaclass
的局限性在于它无法正确识别包 - 导致可能出现空的 metaclass 对象。所以请记住,如果是这种情况,应该修改此功能。dbstack
被多次调用,虽然没有必要(它可以在 setter 中调用一次,然后可以传递结果)。- 不同属性的 setter 代码有 5 行长,大部分都是重复的(属性 名称除外)- 这可以通过抓取 属性 名称来改进通过
dbstack
.