You can do this with a Roslyn Code Analyzer. The following code will create a DiagnosticAnalyzer
that will give a compiler warning if String.EndsWith()
is used.
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ForbiddenMethodsAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor("Forbidden",
"Don't use this method!",
"Use of the '{0}' method is not allowed",
"Forbidden.Stuff",
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "This method is forbidden");
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.InvocationExpression);
}
private static void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context)
{
var invocationExpression = (InvocationExpressionSyntax)context.Node;
var memberAccessExpression = invocationExpression.Expression as MemberAccessExpressionSyntax;
if (memberAccessExpression?.Name.ToString() == "EndsWith")
{
var memberSymbol = context.SemanticModel.GetSymbolInfo(memberAccessExpression).Symbol as IMethodSymbol;
var containingType = memberSymbol.ContainingType;
if (containingType.ContainingNamespace.Name == "System" && containingType.Name == "String")
{
var diagnostic = Diagnostic.Create(Rule, invocationExpression.GetLocation(), memberAccessExpression.ToString());
context.ReportDiagnostic(diagnostic);
}
}
}
}

There are 3 options to use an Analyzer like this:
- Add the
DiagnosticAnalyzer
code directly to your project. It will apply only to that solution.
- Create a
class library with the
DiagnosticAnalyzer
in it, and distribute it
as a Nuget package. It will apply only to solutions that use the package.
- Compile a full VSIX extension containing the
class. The analyzer will work on any solution you load.
This is the first project I've done that uses the Roslyn Code Analysis functionality, so unfortunately I don't understand everything that is going on here. I started with the default Analyzer template and tried various methods, stepped through code, and looked at variables with the watch windows until I found the information I needed for this functionality.
The basic process is to register a SyntaxNode Analysis function, filtered to expressions that invoke a method. Within that method I check to see if the Name
of the MemberAccessExpressionSyntax
being examined is "EndsWith". If it is, I get the ContainingType
that the method is on, and check to see if it is on the String
class in the System
namespace. If it is, I create a Diagnostic
instance from a DiagnosticDescriptor
to tell the IDE where the problem is, and how much of a problem it represents (A warning in this case, I could make it a full Error if I wanted, which would prevent the code from compiling). It is also possible to present the user with different options to automatically fix the error, but I haven't explored that yet.
A lot of the information came from this tutorial, as well as a whole lot of trial and error.