@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.