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