I was recently reading an article by Bill Wagner. At the end of the article Bill covers a common mistake that can cause problems using inline lambda expressions when adding and removing event handlers. Looking at these examples, I thought this would be a perfect case for a code analyzer.

So, the problematic code we are trying to catch is:

source.ProgressChanged += (_, message) => Console.WriteLine(message);
source.ProgressChanged -= (_, message) => Console.WriteLine(message);

To start, we are going to register an action for all AddAssignmentExpression and SubtractAssignmentExpression nodes.

context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.AddAssignmentExpression, SyntaxKind.SubtractAssignmentExpression);

In our method, we are going to grab the node and determine the type so we can use it in our message later.

var assignmentNode = context.Node as AssignmentExpressionSyntax;
string assignmentType = "";

if (assignmentNode.IsKind(SyntaxKind.AddAssignmentExpression))
    assignmentType = "+=";
else if (assignmentNode.IsKind(SyntaxKind.SubtractAssignmentExpression))
    assignmentType = "-=";
else
    return;

Next, we will look at the right hand side of the operation and check if it is a lambda expression. If not, we don't need to analyze any further.

if (!assignmentNode.Right.IsKind(SyntaxKind.ParenthesizedLambdaExpression))
    return;

Finally, if we are at this point, we are either in a += or -= operation with the right hand side being a lambda expression, so we can now raise the diagnostic:

 context.ReportDiagnostic(Diagnostic.Create(Rule, assignmentNode.GetLocation(), assignmentType));

That's all there is to it. The full analyzer code is:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;

namespace EventAnalyzer
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class EventAnalyzerAnalyzer : DiagnosticAnalyzer
    {
        public const string DiagnosticId = "InlineDelegateEventAnalyzer";

        // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
        private static readonly LocalizableString Title = "Don't use += and -= with inline lambda expressions for events";
        private static readonly LocalizableString MessageFormat = "Don't use {0} with inline lambda expressions for events"; 
        private static readonly LocalizableString Description = "Don't use += and -= with inline lambda expressions for events.  If you add an event with a += and an inline lambda expression there is no way to properly remove the handler.  Using -= with an inline lambda expression will not remove an event handler.";
        private const string Category = "Usage";

        private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

        public override void Initialize(AnalysisContext context)
        {
            context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.AddAssignmentExpression, SyntaxKind.SubtractAssignmentExpression);
        }

        private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var assignmentNode = context.Node as AssignmentExpressionSyntax;
            string assignmentType = "";

            if (assignmentNode.IsKind(SyntaxKind.AddAssignmentExpression))
                assignmentType = "+=";
            else if (assignmentNode.IsKind(SyntaxKind.SubtractAssignmentExpression))
                assignmentType = "-=";
            else
                return;

            if (!assignmentNode.Right.IsKind(SyntaxKind.ParenthesizedLambdaExpression))
                return;

            context.ReportDiagnostic(Diagnostic.Create(Rule, assignmentNode.GetLocation(), assignmentType));
        }
    }
}

Thanks to Bill for providing the inspiration for this analyzer and to Dan Smith for pointing me to the article. As you can see, it is very easy to create analyzers for specific situations when you know what to look for.