PropertyWrapper
The PropertyWrapper
aspect provides a simplified way to implement a certain type of Aspect, namely one that decorate a Property to add behavior to it's read and write accessors or control it's storage.
A class decorated with PropertyWrapper
must follow certain rules, and will be turned into an aspect than can then be used in the remaining code, buch in the same way as if the same logic were to be implemented manually using the existing (and more general-use) Cirrus APIs.
The Property Wrapper implementation must be a record
(in Oxygene parlance) or struct (other languages) with a single public property called "Value
", and a public constructor that takes the same type as the property as parameter. The property cannot be abstract, and must not have custom getter or setter code.
The property (and constructor parameter) type may be a fixed type (in which case the wrapper will be limited to be used on properties of that type), or can be generic, allowing the wrapper to apply to a wider range of properties:
type
[PropertyWrapper]
MyWrappedProperty<T> = public record
public
constructor(aValue: T); begin
...
end;
property Value: T read ... write ...
end;
[PropertyWrapper]
public struct MyWrappedProperty<T>
{
public this(T value)
{
...
}
public T Value
{
get { ... }
set { ... }
}
}
@propertyWrapper
public struct MyWrappedProperty<T> {
public init(_ value: T) {
...
}
public var wrappedValue: T {
get { ... }
set { ... }
}
}
@PropertyWrapper
public struct MyWrappedProperty<T>
{
public this(T value)
{
...
}
public T Value
{
__get { ... }
__set { ... }
}
}
<PropertyWrapper>
Public Structure MyWrappedProperty(Of T)
Public Sub New(value As T)
...
End Sub
Public Property Value As T
Get
...
End Get
Set
...
End Set
End Property
End Structure
Optional Aspect Parameters
The property wrapper constructor can optionally take additional parameters. If present, these will become available as parameters on the Aspect, and allow each individual wrapped property to be configured. For example, the following wrapper would store property values under a name, and provide a default value:
type
[PropertyWrapper]
SettingProperty<T> = public record
public
constructor(aValue: T; aName: String; aDefault: T); begin
fName := aName;
fDefault := aDefault;
end;
property Value: T read coalesce(GetSetting(fName), fDefault) write SetSetting(fName, value);
private
fName: String;
fDefault: T;
end;
[PropertyWrapper]
public struct SettingProperty<T>
{
public this(T value, string name, T default)
{
_name = name;
_default = default;
}
public T Value
{
get { return coalesce(GetSetting(_name), default); }
set { SetSetting(_name, value) }
}
}
@propertyWrapper
public struct SettingProperty<T> {
public init(_ value: T, _ name: String, _ default: T) {
_name = name
_default = default
}
public var wrappedValue: T {
get { return coalesce(GetSetting(_name), default) }
set { SetSetting(_name, newValue) }
}
}
@PropertyWrapper
public struct SettingProperty<T>
{
public this(T value, string name, T default)
{
_name = name;
_default = default;
}
public T Value
{
__get { return coalesce(GetSetting(_name), default); }
__set { SetSetting(_name, newValue); }
}
}
<PropertyWrapper>
Public Structure SettingProperty(Of T)
Public Sub New(value As T, name As String, default As T)
_name = name;
_default = default;
End Sub
Public Property Value As T
Get
Return coalesce(GetSetting(_name), default)
End Get
Set
SetSetting(_name, newValue);
End Set
End Property
End Structure
And you can use it like this:
[SettingProperty("Size", 5)]
property Size: Integer;
[SettingProperty("Size", 5)]
public int Size { get; set; }
@SettingProperty("Size", 5)
public var Size: Int
@SettingProperty("Size", 5)
public int Size { __get; __set; }
<SettingProperty("Size", 5)>
Public property Size: Integer
Access to self
/this
/Me
Sometimes it is helpful kr even crucial for property code to have access to the instance containing the property, from inside the property wrapper code. To achieve this, an optional second (or only) generic parameter can be provided, and used as parameter on the property (in languages that support named index properties, which excludes C# and Java):
type
[PropertyWrapper]
MyWrappedProperty<T, TSelf> = public record
public
constructor(aValue: T); begin
...
end;
property Value[aSelf: TSelf]: T read ... write ...
end;
@propertyWrapper
public struct MyWrappedProperty<T, TSelf> {
public init(_ value: T) {
...
}
public subscript wrappedValue(_ _self: TSelf): T {
get { ... }
set { ... }
}
}
<PropertyWrapper>
Public Structure MyWrappedProperty(Of T, TMe)
Public Sub New(value As T)
...
End Sub
Public Property(_me As TMe) Value As T
Get
...
End Get
Set
...
End Set
End Property
End Structure
In must cases, that generic parameter will probably need a constraint, in order to make any useful actions on the passed variable feasible.
Swift Compatibility
For compatibility with Apple Swift, the name of the aspect may be camelCased in Swift (@propertyWrapper
), and the name wrappedValue
may be used for the property.
Defined in RemObjects.Elements.Cirrus.dll
See Also
- Aspects
- Properties in Oxygene
- SE-0258: Property Wrappers in Apple Swift