@ck, @mh, I am now working (struggling) a while with aspects, and the biggest problem is to generate code - generating a line of code is not something you easily do within Cirrus; generating a complete method becomes really time consuming to get it right.
I know that the reason for this is to be language independent; the aspect can be applied on any RemObjects language because of this type library as the code is generated from it.
But I was thinking that this could be much easier to implement.
If I was able to create static methods without any dependencies in a separate code unit within the aspect dll , that you program in the language that is used to create the aspect, this code could be compiled into a separate code unit that is linked to the project where the aspect is applied.
To do so, the code has to be compiled into the aspect dll as plain text.
I take the ExtensionField code as an example.
In this code I can add the following unit:
namespace builditAspects;
[ApplyOnAspect(ExtensionField)]
type ExtensionFieldsMethods<S, V> = public static class
method GetKeyOnTarget(findKey: S; Dictionary: System.Collections.Generic.Dictionary<WeakReference, V>);
begin
var ToRemove := new System.Generic.List(System.WeakReference);
For each k: System.WeakReference in Test_Dict.Keys do
begin
if k.Target = self then
begin
result := k;
break;
end
else
if k.Target = nil then
ToRemove.Add(k);
end;
For each k1: System.WeakReference in ToRemove do
Test_Dict.Remove(k1);
end;
method Get_Field_Value(key: S; Dictionary: System.Collections.Generic.Dictionary<WeakReference, V>): Object;
begin
var key := GetKeyOnTarget(key, Dictionary);
if key <> nil then
exit Test_Dict[key]
else
exit default;
end;
method Set_Field_Value(key: S; val: V; Dictionary: System.Collections.Generic.Dictionary<WeakReference, V>);
begin
var key := GetKeyOnTarget(key, Dictionary);
if key <> nil then
Dictionary.Keys[key] := val
else if val <> nil then
Dictionary.Add(self, val);
end;
end;
end.
The attribute [ApplyOnAspect(ExtensionField)] is telling the compiler to save a plain text cody of this code and compile it as a resource in the dll with the name ExtensionField.res.
Then I build the aspect - which is now much simpler:
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 Methods := new TypeValue(Services.FindType("builditAspects.ExtensionFieldsMethods`2"));
//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);
//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;
lWrite.ReplaceMethodBody(
new BeginStatement(
[new StandaloneStatement(
new ProcValue(Methods, "Set_Field_Value", [new ParamValue(0), new ParamValue(1), StorageField], [SelfType, aField.Type])
)]
));
//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;
lRead.ReplaceMethodBody(new BeginStatement(
new BeginStatement(
[new StandaloneStatement(
new ProcValue(Methods, "Set_Field_Value", [new ParamValue(0), StorageField], [SelfType, aField.Type])
)]
));
//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.
As soon as the aspect is applied (changed or added code) the text resource is added to a unit and compiled for the used platform, making the static methods available for the aspect code.
Because of the compilation of the code to an object that is linked into the rest of the code, this also works for any language.