Again, with lots of help from @ck
Edit: This aspect does only work on .Net, because it uses the .Net class System.WeakReference
With extensions, you can add methods and functional properties to a class.
But it is not possible to add storage to it, so no fields and no storage properties.
With this aspect you get the ability to add those fields to a class, with - according to @mh - a little black magic .
Usage in the code:
> type MyExtension = public extension(MyClass)
> //Add an extension field
> [ExtensionField]
> public var MyExtensionField as MyType;
> end;
The code for the aspect (@mh, @ck - somthing for Cirrus?):
namespace builditAspects;
interface
uses
System.Linq,
RemObjects.Elements.Cirrus.*;
type
[AttributeUsage(AttributeTargets.Field)]
ExtensionField = public class(Attribute, IFieldInterfaceDecorator)
private
public
method HandleInterface(Services: IServices; aField: IFieldDefinition);
end;
implementation
method ExtensionField.HandleInterface(Services: IServices; aField: IFieldDefinition);
begin
//Needed in all of the code
var SelfType := aField.Owner.ExtensionTypeFor;
var WeakReferenceType := Services.FindType("System.WeakReference");
var stmt: System.Collections.Generic.List<Statement>;
//Add the private storage dictionary
var t := Services.FindType("System.Collections.Generic.Dictionary`2");
var t1 := Services.CreateGenericInstance(t, WeakReferenceType, aField.Type); //initialize the generic parameters of the dictionary
var StorageField := aField.Owner.AddField(aField.Name + "_Dict", t1, true);
StorageField.Visibility := Visibility.Private;
// -> this part does not work; no assignment is generated (also no error is raised)
StorageField.InitialValue := new NewValue(t1);
if aField.Owner.GetClassConstructor = nil then aField.Owner.AddConstructor(true);
//find key method (does also the clean up)
var lFindKey := aField.Owner.AddMethod(aField.Name + "_GetKeyOnTarget", WeakReferenceType, true);
lFindKey.AddParameter("self", ParameterModifier.In, SelfType);
lFindKey.Virtual := VirtualMode.None;
lFindKey.Visibility := Visibility.Private;
stmt := new System.Collections.Generic.List<Statement>;
//ToRemove := new System.Generic.List(System.WeakReference);
var lt := Services.FindType("System.Collections.Generic.List`1");
var lt1 := Services.CreateGenericInstance(lt, WeakReferenceType); //initialize the generic parameters of the list
stmt.Add(new AssignmentStatement(new IdentifierValue("ToRemove"), new NewValue(lt1)));
//For each k: System.WeakReference in Test_Dict.Keys do
//begin
// if k.Target = self then
// exit k
// else
// if k.Target = nil then
// ToRemove.Add(k);
//end;
var body:= new BeginStatement(
new IfStatement(
new BinaryValue(new IdentifierValue(new IdentifierValue("k"), "Target"), new ParamValue(0), BinaryOperator.Equal),
new ExitStatement(new IdentifierValue("k")),
new IfStatement(
new BinaryValue(new IdentifierValue(new IdentifierValue("k"), "Target"), new NilValue, BinaryOperator.Equal),
new StandaloneStatement(new ProcValue(new IdentifierValue("ToRemove"), "Add", [new IdentifierValue("k")]))
)
)
);
stmt.Add(new ForInStatement( "k", //aName: String; -> the var for the foreach
WeakReferenceType, //aType: IType; -> type of the previous one
false, //aMatching: Boolean; -> use matchin
new IdentifierValue(new FieldValue(nil, StorageField), "Keys"),//aSource: Value; -> the enummerable
body, //aBody: Statement; -> statement in the for
nil, //aIndexName: String; -> indexname
nil, //aIndexType: IType; -> type of the previous, always int32
false //aParallel: Boolean -> use a parallel for
)
);
//For each k1: System.WeakReference in ToRemove do
// Test_Dict.Remove(k1);
var body2:= new StandaloneStatement(new ProcValue(new FieldValue(nil, StorageField),"Remove", [new IdentifierValue("k1")]));
stmt.Add(new ForInStatement( "k1", //aName: String; -> the var for the foreach
WeakReferenceType, //aType: IType; -> type of the previous one
false, //aMatching: Boolean; -> use matchin
new IdentifierValue("ToRemove"), //aSource: Value; -> the enummerable
body2, //aBody: Statement; -> statement in the for
nil, //aIndexName: String; -> indexname
nil, //aIndexType: IType; -> type of the previous, always int32
false //aParallel: Boolean -> use a parallel for
)
);
lFindKey.ReplaceMethodBody(new BeginStatement([new LocalVariable("ToRemove", lt1)],stmt.ToArray));
//private write method for field replacing property
var lWrite := aField.Owner.AddMethod('set_' + aField.Name, nil, aField.Static);
lWrite.AddParameter('self', ParameterModifier.In, SelfType).Prefix := '$mapped';
lWrite.AddParameter('val', ParameterModifier.In, aField.Type);
lWrite.Virtual := VirtualMode.None;
lWrite.Visibility := aField.Visibility;
stmt := new System.Collections.Generic.List<Statement>;
//key := Test_GetKeyOnTarget(self);
stmt.Add(new AssignmentStatement(new IdentifierValue("key"), new ProcValue(new TypeValue(aField.Owner), lFindKey, [new ParamValue(0)])));
//if key <> nil then
// Test_Dict.Keys[key] := val
//else if val <> nil then
// Test_Dict.Add(self, val);
stmt.Add(new IfStatement(
new BinaryValue(new IdentifierValue("key"), new NilValue, BinaryOperator.NotEqual),
new AssignmentStatement(new SubArrayValue(new IdentifierValue(new FieldValue(nil, StorageField), "Item"), new IdentifierValue("key")),new ParamValue(1)),
new IfStatement(
new BinaryValue(new ParamValue(1), new NilValue, BinaryOperator.NotEqual),
new StandaloneStatement(new ProcValue(new FieldValue(nil, StorageField),"Add", [new NewValue(WeakReferenceType, new ParamValue(0)), new ParamValue(1)]))
)
)
);
lWrite.ReplaceMethodBody(new BeginStatement([new LocalVariable("key", WeakReferenceType)], stmt.ToArray));
//private read method for field replacing property
var lRead := aField.Owner.AddMethod('get_' + aField.Name, aField.Type, aField.Static);
lRead.AddParameter('self', ParameterModifier.In, SelfType).Prefix := '$mapped';
lRead.Virtual := VirtualMode.None;
lRead.Visibility := aField.Visibility;
stmt := new System.Collections.Generic.List<Statement>;
//key := Test_GetKeyOnTarget(self);
stmt.Add(new AssignmentStatement(new IdentifierValue("key"), new ProcValue(new TypeValue(aField.Owner), lFindKey, [new ParamValue(0)])));
//if key <> nil then
// exit Test_Dict[key]
//else
// exit default;
stmt.Add(new IfStatement(
new BinaryValue(new IdentifierValue("key"), new NilValue, BinaryOperator.NotEqual),
new ExitStatement(new SubArrayValue(new IdentifierValue(new FieldValue(nil, StorageField), "Item"), new IdentifierValue("key"))),
new ExitStatement(new DefaultValue(aField.Type)))) ;
lRead.ReplaceMethodBody(new BeginStatement([new LocalVariable("key", WeakReferenceType)], stmt.ToArray));
//the field replacing property - visibility the same as the original field (works 100%)
var lProp := aField.Owner.AddProperty(aField.Name, aField.Type, aField.Static);
lProp.Locked := true;
lProp.ReadMethod := lRead;
lProp.WriteMethod := lWrite;
lProp.Visibility := aField.Visibility;
//remove the original field (works 100%)
aField.Owner.RemoveField(aField);
end;
end.