Translate

miércoles, 17 de mayo de 2023

SymbolVisitor

SymbolVisitor es el análogo de SyntaxVisitor, pero se aplica a nivel de símbolo. Desafortunadamente, a diferencia de SyntaxWalker y CSharpSyntaxRewriter, cuando usamos SymbolVisitor debemos construir el código de andamiaje para visitar todos los nodos.

Para enumerar simplemente todos los tipos disponibles para una compilación, podemos usar lo siguiente.


public class NamedTypeVisitor : SymbolVisitor

{

    public override void VisitNamespace(INamespaceSymbol symbol)

    {

        Console.WriteLine(symbol);

        

        foreach(var childSymbol in symbol.GetMembers())

        {

            //We must implement the visitor pattern ourselves and 

            //accept the child symbols in order to visit their children

            childSymbol.Accept(this);

        }

    }


    public override void VisitNamedType(INamedTypeSymbol symbol)

    {

        Console.WriteLine(symbol);

        

        foreach (var childSymbol in symbol.GetTypeMembers())

        {

            //Once againt we must accept the children to visit 

            //all of their children

            childSymbol.Accept(this);

        }

    }

}


//Now we need to use our visitor

var tree = CSharpSyntaxTree.ParseText(@"

class MyClass

{

    class Nested

    {

    }

    void M()

    {

    }

}");


var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);

var compilation = CSharpCompilation.Create("MyCompilation",

    syntaxTrees: new[] { tree }, references: new[] { mscorlib });


var visitor = new NamedTypeVisitor();

visitor.Visit(compilation.GlobalNamespace);


Para visitar todos los métodos disponibles para una compilación dada podemos utilizar los siguientes:


public class MethodSymbolVisitor : SymbolVisitor

{

    //NOTE: We have to visit the namespace's children even though

    //we don't care about them. 😦

    public override void VisitNamespace(INamespaceSymbol symbol)

    {

        foreach(var child in symbol.GetMembers())

        {

            child.Accept(this);

        }

    }

    

    //NOTE: We have to visit the named type's children even though

    //we don't care about them. 😦

    public override void VisitNamedType(INamedTypeSymbol symbol)

    {

        foreach(var child in symbol.GetMembers())

        {

            child.Accept(this);

        }

    }


    public override void VisitMethod(IMethodSymbol symbol)

    {

        Console.WriteLine(symbol);

    }

}

Es importante tener en cuenta cómo  se debe estructurar el código para poder visitar todos los símbolos que interesan. Si estoy interesado en visitar símbolos de métodos, no quiero tener que escribir código que visite espacios de nombres y tipos.

Con suerte, en algún momento obtendremos una clase SymbolWalker que podamos usar para separar nuestra implementación del código transversal. 

¿Cómo obtenemos una lista de todos los tipos disponibles para una compilación? así :


public class CustomSymbolFinder

{

    public List<INamedTypeSymbol> GetAllSymbols(Compilation compilation)

    {

        var visitor = new FindAllSymbolsVisitor();

        visitor.Visit(compilation.GlobalNamespace);

        return visitor.AllTypeSymbols;

    }


    private class FindAllSymbolsVisitor : SymbolVisitor

    {

        public List<INamedTypeSymbol> AllTypeSymbols { get; } = new List<INamedTypeSymbol>();


        public override void VisitNamespace(INamespaceSymbol symbol)

        {

            Parallel.ForEach(symbol.GetMembers(), s => s.Accept(this));

        }


        public override void VisitNamedType(INamedTypeSymbol symbol)

        {

            AllTypeSymbols.Add(symbol);

            foreach (var childSymbol in symbol.GetTypeMembers())

            {

                base.Visit(childSymbol);

            }

        }

    }

}

La clase SymbolVisitor probablemente sea apropiada para usos únicos durante la compilación o para visitar un subconjunto de símbolos disponibles. Por lo menos, vale la pena ser consciente de ello.