Function Request: ExposeEvents and Handles

Then I am going to test it now.

It is not recognizing the aspects - do I have to reference something?
image

Did you see my project consisted of two projects? You need to reference the class library.

Ok, I thought you added it to the language.

I donā€™t see the Aspect code with the expose events ā€¦
(Maybe I am overlooking it, I am not on my best today - even problems with typing)

Check ClassLibrary1/Class1.pas.

btw I can add this to the base cirrus library (so itā€™s always available). Maybe in a VB Compatibility namespace, but i want to be sure it works perfectly before we do that. Hence the external library for now. Once we know it works fully Iā€™ll copy it into base Cirrus.

Ok, clear - now I see the zip ā€¦

1 Like

Carlo,
How do I debug the Aspect code?

Easiest: Add calls to Services.Warning( ā€¦) to emit info to the compile log.
Harder: Open a new VS instance with the aspect code open. Debug/Attach to all the msbuild processes and set the breakpoints (make sure Tools/Environment Options/Debug/Just my code is disabled)

Works.

But the lMethod.GetAttribute(j) returns a proxy object.
If I want to see a value I get ā€œEvaluating this expression requires decoding a proxy object. Decoding transparent proxies is not supported.ā€

Point is that I want a parameter of type Object in the attribute.
And that causes the line var lEventName := lAtt.GetParameter(0); to throw a System.NullReferenceException.

Any hints how to use a non string parameter on the attribute?

Can yuo show the exact situation (or give me the code that shows this) ?

The proxy object thing is an unfortunate issue with .NET and canā€™t really be helped.

type
  [AttributeUsage(AttributeTargets.Method)]
  HandlesAspect = public class(Attribute, IMethodInterfaceDecorator)
  private
  protected
  public
    constructor(aEvent: Object); //Changed this one.
    begin 
    end;

    method HandleInterface(Services: IServices; aMethod: IMethodDefinition);
    begin 
      // Does nothing. 
    end;
  end;

  [AttributeUsage(AttributeTargets.Field)]
  ExposeEventsAspect = public class(Attribute, IFieldInterfaceDecorator)
  private
  protected
  public
    method GetBody(aServices: IServices; aField: IFieldDefinition; aRemove: Boolean): Statement;
    begin 
      var lRes := new BeginStatement;

      for i: Integer := 0 to aField.Owner.MethodCount -1 do begin
        var lMethod := aField.Owner.GetMethod(i);
        
        //if lMethod.Static then continue; events can be static!

        for j: Integer := 0 to lMethod.AttributeCount -1 do begin
          var lAtt: IAttributeDefinition := lMethod.GetAttribute(j);
          if lAtt.Type.Name = 'HandlesAspect' then begin
            var lEventName := lAtt.GetParameter(0); //ERROR HERE
            var pos := lEventName.ToString.LastIndexOf(".");
            if lEventName.ToString.Substring(0,pos).tolower = aField.Name.tolower then begin
              var lType := aField.Type;
              var lEvent: IEvent;
              while lType <> nil do begin
                lEvent := lType.GetEvents(lEventName.ToString).FirstOrDefault;
                if lEvent <> nil then break;
                lType := lType.ParentType;
              end;
              
              if lEvent = nil then begin 
                aServices.EmitError('Event '+lEventName+ ' not found!');
                exit;
              end;
              lRes.Add(new StandaloneStatement(new ProcValue(new IdentifierValue(aField.Name), (if aRemove then 'remove_' else 'add_') + lEventName, new NewValue(lEvent.Type, new IdentifierValue(lMethod.Name)))));
            end;
          end;

        end;
      end;
      exit lRes;
    end;

    method HandleInterface(Services: IServices; aField: IFieldDefinition);
    begin
      var lProp := aField.Owner.AddProperty(aField.Name, aField.Type, false);
      var lRead := aField.Owner.AddMethod('get_' + aField.Name, aField.Type, false);
      var lWrite := aField.Owner.AddMethod('set_' + aField.Name, nil, false);
      lWrite.AddParameter('val', ParameterModifier.In, aField.Type);
      lProp.ReadMethod := lRead;
      lProp.WriteMethod := lWrite;
      lRead.ReplaceMethodBody(new ExitStatement(new FieldValue(new SelfValue, aField)));
      var lRemove := GetBody(Services, aField, true);
      var lAdd := GetBody(Services, aField, false);
      aField.Name := '_'+aField.Name;
      lWrite.ReplaceMethodBody(new BeginStatement(
        new IfStatement(new BinaryValue(new FieldValue(new SelfValue, aField), new ParamValue(0), BinaryOperator.Equal), new ExitStatement),
        new IfStatement(new BinaryValue(new FieldValue(new SelfValue, aField), new NilValue, BinaryOperator.NotEqual), 
          lRemove
        ),
        new AssignmentStatement(new FieldValue(new SelfValue, aField), new ParamValue(0)),
        new IfStatement(new BinaryValue(new FieldValue(new SelfValue, aField), new NilValue, BinaryOperator.NotEqual), 
          lAdd
        )
      ));
    end;
  end;

The point is that I need the parameter value (in my case btnClose.Click).

What would this contain on the caller side? Odds are there was a compiler error there, since Object parameters arenā€™t really allowed on attributes.

On the code:
[Handles(btnBewaren.Click)]

This way the CC works on the aspect.

yeah but the compiler doesnā€™t allow compilnig that. Click isnā€™t typed and would give an error. The aspect just needs better error checking (making sure .ParamCount > 0)

Did that, and paramcount = 1.
No compile error on the aspect.

I see now that a Constant value is expected for an aspect.
Makes it almost unusable ā€¦

you could just use namedof() that results in a string
[Handles(nameof(btnBewaren), nameof(btnBewaren.Click)]