Skip to content

Instantly share code, notes, and snippets.

@JohanLarsson
Last active August 10, 2019 19:01
Show Gist options
  • Save JohanLarsson/a5c2a0f70b6198dfa60e69dbcf959f2b to your computer and use it in GitHub Desktop.
Save JohanLarsson/a5c2a0f70b6198dfa60e69dbcf959f2b to your computer and use it in GitHub Desktop.

Use case (picked one randomly, there are many)

An analyzer that a property notifies INPC correctly. Given the following code:

public class C : INotifyPropertyChanged
{
    private int p;

    public event PropertyChangedEventHandler PropertyChanged;

    public int P
    {
        get => this.p;
        set
        {
            if (value == this.p)
            {
                return;
            }
            
            this.p = value;
            this.OnPropertyChanged();
        }
    }

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

The analyzer registers context.RegisterSyntaxNodeAction(c => Handle(c), SyntaxKind.PropertyDeclaration); and checks the setter:

  1. Walks the setter to find all InvpocationExpressionSyntax. And for each:
    1. Gets the IMethodSymbol of the InvpocationExpressionSyntax
    2. Get the MethodDeclarationSyntax for the IMethodSymbol via SyntaxReference if it exits.
    3. Walks the method declaration to see that it notifies correctly for the expected property.

Problem

The problem above is that in 1.3 there are no guarantees that the SemanticModel can be used. If the MethodDeclarationSyntax is in a base class or partial in another document, using the semantic model will throw. This is easily solved, we check if (semanticmodel.SyntaxTree != declaration.SyntaxTree) and call Compilation.GetSemanticModel(declaration.SyntaxTree) to get a model we can use.

GetSemanticModel is an expensive call. I solved this by adding memoization. Memoization is nontrivial as the cache needs to be emptied on compilation end and even then there may be memory issues in keeping syntax trees alive. I implemented this via an analyzer with RegisterCompilationStartAction and finalizer and refcounting, pretty messy. The performance improvements are great, order of magnitude but Cyrus is worried about the implementation and rightfully so. It is not ideal to have analyzers do this caching.

Potential solutions

  • Have an API for getting symbols for nodes in any tree.
  • Provide memoization API
  • Optimize GetSemanticModel
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment