locked
Identifying event handlers RRS feed

  • Question

  •  

    Within the IDE , Visual Basic code is generated for eventhandlers with aHandles clause.

     

    Is there any FxCop way of identifying these eventhandlers ? (Still using VS2005 and built in Code Analysis)

    Friday, February 22, 2008 9:33 AM

Answers

  • After thinking about this some more I think the rule you actually want is something that enforces that delegates and the methods that implement/satisfy them have the same parameter names. I say this because in the code sample you gave me what is actually going on is when you declare the ABC event an ABCEventHandler delegate with those parameter names is being created in the background.

     

    With that said the following is a code sample showing how you might write a rule like that. This example rule is provided as is and is use at your own risk. It has not been tested and probably has some bugs but should be enough to point you in the right direction.

     

    Code Snippet

    internal sealed class DelegateImplementationsShouldHaveMatchingParameterNames : MyFxCopBaseRule

    {

      public DelegateImplementationsShouldHaveMatchingParameterNames()

        : base("DelegateImplementationsShouldHaveMatchingParameterNames")

      { }

     

      public override TargetVisibilities TargetVisibility

      {

        get { return TargetVisibilities.All; }

      }

     

      public override ProblemCollection Check(Member member)

      {

        Method method = member as Method;

        if (method == null)

          return null;

        Visit(method);

        return Problems;

      }

     

      public override void VisitConstruct(Construct construct)

      {

        DelegateNode @delegate = construct.Type as DelegateNode;

        if (@delegate != null)

        {

          ExpressionVisitor expressionVisitor = new ExpressionVisitor();

          Method method = expressionVisitor.GetMethodImplementingDelegate(construct);

          if (method != null)

          {

            ParameterCollection delegateParameters = @delegate.Parameters;

            ParameterCollection methodParameters = method.Parameters;

            Debug.Assert(delegateParameters.Count == methodParameters.Count, "Delegate and implementing method parameters count doesn't match! This should have been a compiler error.");

            for (int i = 0; i < delegateParameters.Count; i++)

            {

              if (!string.Equals(delegateParameters[i].Name.Name, methodParameters[i].Name.Name, StringComparison.OrdinalIgnoreCase))

              {

                ReportViolation(construct, @delegate, method);

                break;

              }

            }

          }

        }

        base.VisitConstruct(construct);

      }

     

      private void ReportViolation(Construct construct, DelegateNode @delegate, Method method)

      {

        Resolution resolution = GetResolution(@delegate, method);

        Problem problem = new Problem(resolution, construct.SourceContext);

        Problems.Add(problem);

      }

     

      private class ExpressionVisitor : BinaryReadOnlyVisitor

      {

        private Method m_method;

        public Method GetMethodImplementingDelegate(Construct construct)

        {

          m_method = null;

          VisitExpression(construct.Operands[1]);

          return m_method;

        }

     

        public override void VisitExpression(Expression expression)

        {

          if(m_method != null)

            return;

          if (expression.Type != FrameworkTypes.IntPtr)

          {

            base.VisitExpression(expression);

            return;

          }

          MemberBinding methodBinding = null;

          UnaryExpression unaryExpression = expression as UnaryExpression;

          if (unaryExpression != null)

          {

            methodBinding = unaryExpression.Operand as MemberBinding;

          }

          else

          {

            BinaryExpression binaryExpression = expression as BinaryExpression;

            if (binaryExpression != null)

            {

              methodBinding = binaryExpression.Operand2 as MemberBinding;

            }

          }

          if (methodBinding != null)

          {

            Method method = methodBinding.BoundMember as Method;

            if (method != null && m_method == null)

            {

              m_method = method;

              return;

            }

          }

        }

      }

    }

     

     

     

     

    -Todd

    Friday, March 21, 2008 12:20 AM
    Moderator

All replies

  • FilthyZombie,

    I believe it is possible to identify event handlers.  If you were to create an event handler with three parameters, FXCop would fire a CA1801: ReviewUnusedParameters.

    How you would go about doing that though, I'm not sure.  What are you trying to accomplish?

    Thanks,


    Steve - www.platinumbay.com
    Monday, February 25, 2008 3:40 AM
  •  

    CA1009 requires event declarations to be of a standard format.

     

    We simply require that any (explicit) event handler matches the required signature

     

    We also wish to ensure that any internal events match their (non-standard) required signatures. Is anyone aware of how I would go about checking what the required signature is.

     

    This is similar to the requirement that overrides etc use the same parameter names as the base class.(CA1725)

    Monday, February 25, 2008 11:38 AM
  • Can you provide some code samples of things you want to fire on and things you don't want to fire on? I'm still not sure I understand what you are looking for.
    Thursday, February 28, 2008 6:12 PM
    Moderator
  • Friend Class MyFriendClass

    Public Event ABC(ByRef cancel as Boolean)

    End Class

     

    Public Class MyPublicClass

     

    Public Event abc As EventHandler

     

    Private WthEvents objFriend As MyFriendClass

     

    Private objFriend_ABC(ByRef continue As Boolean) Handles objFriend.ABC

    continue = True

    End Sub

     

    Private Dummy()

    Addhandler objFriend.ABC, AddressOf DummyHandler

    End Sub

     

    Private Sub DummyHandler(ByRefcontinue As Boolean)

    RaiseEvent abc(Me, EventArgs.Empty)

    End Sub

    End Class

     

    As you can see, using the wrong name for a parameter can affect the semantics

     

    objFriend_ABC is generated automatically and it is this method I wish to identify and then check that its parameters match the (pre)defined signature of MyFriendClass.ABC

     

    If possible I would also like to check dummyHandler - but I appreciate that we may not be able to identify that it is used as a delegate.

     

     

    Thursday, February 28, 2008 8:00 PM
  • Sorry for the long delay on this and even worse I'm afraid I'm still confused as to what exactly you are trying to check for. It is a compiler error to use the Handles keyword or the AddHandler statement with a method that doesn't match the signature of the specified event so it seems like the compiler may already be doing this check for you?

     

    Do you mean you want to verify the parameter names of the event handler match those of the event itself?

     

    -Todd

    Tuesday, March 18, 2008 8:21 PM
    Moderator
  • Todd,

     

    exactly, sorry it wasn't clearer.

     

    In the example I gave the subtle difference beteween "cancel" and "continue" as the parameter name can make a huge difference to the outcome of the event !!

     

    One caveat I have thought of is when a method "handles" multiple events - but assuming I can check then I can check against each and every event for signature semantics.

     

    Thanks in advance

     

    Wednesday, March 19, 2008 7:00 AM
  • After thinking about this some more I think the rule you actually want is something that enforces that delegates and the methods that implement/satisfy them have the same parameter names. I say this because in the code sample you gave me what is actually going on is when you declare the ABC event an ABCEventHandler delegate with those parameter names is being created in the background.

     

    With that said the following is a code sample showing how you might write a rule like that. This example rule is provided as is and is use at your own risk. It has not been tested and probably has some bugs but should be enough to point you in the right direction.

     

    Code Snippet

    internal sealed class DelegateImplementationsShouldHaveMatchingParameterNames : MyFxCopBaseRule

    {

      public DelegateImplementationsShouldHaveMatchingParameterNames()

        : base("DelegateImplementationsShouldHaveMatchingParameterNames")

      { }

     

      public override TargetVisibilities TargetVisibility

      {

        get { return TargetVisibilities.All; }

      }

     

      public override ProblemCollection Check(Member member)

      {

        Method method = member as Method;

        if (method == null)

          return null;

        Visit(method);

        return Problems;

      }

     

      public override void VisitConstruct(Construct construct)

      {

        DelegateNode @delegate = construct.Type as DelegateNode;

        if (@delegate != null)

        {

          ExpressionVisitor expressionVisitor = new ExpressionVisitor();

          Method method = expressionVisitor.GetMethodImplementingDelegate(construct);

          if (method != null)

          {

            ParameterCollection delegateParameters = @delegate.Parameters;

            ParameterCollection methodParameters = method.Parameters;

            Debug.Assert(delegateParameters.Count == methodParameters.Count, "Delegate and implementing method parameters count doesn't match! This should have been a compiler error.");

            for (int i = 0; i < delegateParameters.Count; i++)

            {

              if (!string.Equals(delegateParameters[i].Name.Name, methodParameters[i].Name.Name, StringComparison.OrdinalIgnoreCase))

              {

                ReportViolation(construct, @delegate, method);

                break;

              }

            }

          }

        }

        base.VisitConstruct(construct);

      }

     

      private void ReportViolation(Construct construct, DelegateNode @delegate, Method method)

      {

        Resolution resolution = GetResolution(@delegate, method);

        Problem problem = new Problem(resolution, construct.SourceContext);

        Problems.Add(problem);

      }

     

      private class ExpressionVisitor : BinaryReadOnlyVisitor

      {

        private Method m_method;

        public Method GetMethodImplementingDelegate(Construct construct)

        {

          m_method = null;

          VisitExpression(construct.Operands[1]);

          return m_method;

        }

     

        public override void VisitExpression(Expression expression)

        {

          if(m_method != null)

            return;

          if (expression.Type != FrameworkTypes.IntPtr)

          {

            base.VisitExpression(expression);

            return;

          }

          MemberBinding methodBinding = null;

          UnaryExpression unaryExpression = expression as UnaryExpression;

          if (unaryExpression != null)

          {

            methodBinding = unaryExpression.Operand as MemberBinding;

          }

          else

          {

            BinaryExpression binaryExpression = expression as BinaryExpression;

            if (binaryExpression != null)

            {

              methodBinding = binaryExpression.Operand2 as MemberBinding;

            }

          }

          if (methodBinding != null)

          {

            Method method = methodBinding.BoundMember as Method;

            if (method != null && m_method == null)

            {

              m_method = method;

              return;

            }

          }

        }

      }

    }

     

     

     

     

    -Todd

    Friday, March 21, 2008 12:20 AM
    Moderator
  • Todd,

     

    Many thanks - it did just what I needed.

     

    I guess we got the terminology correct in the end.

     

    I just had to tweak it slightly - convert to VB.NET and modify to work with VS2005 !! (I've had plenty of practice at that !!)

     

    FilthyZombie

     

    Friday, March 21, 2008 6:22 PM