How can I get the AssemblyQualifiedName of a Rosyln.Compilers.CSharp.Symbol?

Con risposta How can I get the AssemblyQualifiedName of a Rosyln.Compilers.CSharp.Symbol?

  • Tuesday, November 08, 2011 1:38 AM
     
     

    In my VS extension I'm trying to determine if the type of a symbol in source is the same as a known type. Is there a way I can get the AssemblyQualifiedName of whatever abstraction Roslyn uses to express a type (Rosyln.Compilers.CSharp.Symbol) so I can compare it with the AssemblyQualifiedName of the known type? Do I have to construct the AssemblyQualifiedName myself?

    Comparing types is such a common operation I'm figuring there must be some simple convention for doing it ala o.GetType() == typeof(KnownType). 

    Thanks,
    Chris 

All Replies

  • Tuesday, November 08, 2011 9:27 PM
     
     Answered

    Symbols overload operator==, so in general you can compare two types to see if they are the same with either == or Equals. So perahsp the easiest way is to use Compilation.GetTypeByMetadataName to get the type you are interested it, then compare with == or Equals.

    For a small, fixed set of "special types", like Int32 and String, a property TypeSymbol.SpecialType lets you even more easily see if a type is one of those special types.

    • Proposed As Answer by Matt Warren - MSFT Wednesday, November 09, 2011 4:02 AM
    • Marked As Answer by kingces95 Thursday, November 10, 2011 11:52 PM
    •  
  • Tuesday, November 08, 2011 9:58 PM
    Owner
     
     Proposed

    Hi Chris,

    There is currently no in-built way in Roslyn to map / compare TypeSymbols to the corresponding System.Type. However there are some alternatives that you should be able to use -

    1. If you are trying to compare 'special' types like int, string etc. you can do this rather easily using code like below -

    if (typeSymbol.SpecialType == Roslyn.Compilers.SpecialType.System_String)

    The Roslyn.Compilers.SpecialType enumeration includes entries for a bunch of types including System.Object, System.Collections.Generic.IList<T> etc.

    2. For more advanced comparisons, you could write some code that uses the Roslyn Symbol Display API in conjunction with Roslyn.Compilers.AssemblyIdentity.Equivalent() as shown below to compare types.

    Note: The below code is just an example - you would probably need to update it if you want to handle all possible types of TypeSymbols (such as generic types etc.)...

    static System.Reflection.AssemblyName GetAssemblyName(TypeSymbol typeSymbol)
    {
        System.Reflection.AssemblyName assemblyName = null;

        switch (typeSymbol.Kind)
        {
            case SymbolKind.ArrayType:
                var elementType = ((ArrayTypeSymbol)typeSymbol).ElementType;
                assemblyName = GetAssemblyName(elementType);
                break;

            case SymbolKind.TypeParameter:
                var typeParameterType = (TypeParameterSymbol)typeSymbol;
                TypeSymbol containingType =
                    typeParameterType.DeclaringType ??
                    typeParameterType.DeclaringMethod.ContainingType;
                assemblyName = GetAssemblyName(containingType);
                break;

            // May need similar special handling for other kinds of TypeSymbols.

            default:
                assemblyName = typeSymbol.ContainingAssembly.AssemblyName;
                break;
        }

        return assemblyName;
    }

    static bool IsTypeString(TypeSymbol typeSymbol)
    {
        Console.WriteLine("------------------------------------------------------------");
        var typeIsString = false;

        // May need to tweak the SymbolDisplayFormat to make it work for all cases
        // For example, more work will be required for generic types...
        var sourceType = typeSymbol.ToDisplayString(
            new SymbolDisplayFormat(
                typeQualificationStyle:
                    SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
                miscellaneousOptions:
                    SymbolDisplayMiscellaneousOptions.None));

        var targetType = typeof(string).FullName;
        var sourceAssemblyName = GetAssemblyName(typeSymbol);
        var targetAssemblyName = System.Reflection.Assembly.GetAssembly(
            targetType.GetType()).GetName();

        Console.WriteLine("Source Type: " + sourceType);
        Console.WriteLine("Target Type: " + targetType);
        Console.WriteLine("Source Assembly: " + sourceAssemblyName.Name);
        Console.WriteLine("Target Assembly: " + targetAssemblyName.Name);

        if (sourceType == targetType &&
            Roslyn.Compilers.AssemblyIdentity.Equivalent(
                sourceAssemblyName, targetAssemblyName))
        {
            Console.WriteLine("Yes: Type is String");
            typeIsString = true;
        }
        else
        {
            Console.WriteLine("No: Type is not String");
        }

        Console.WriteLine("------------------------------------------------------------");
        return typeIsString;
    }

    static void Main()
    {
        var source = @"
        using System;
        using System.Collections.Generic;
        class Program
        {
            static void Foo(string a)
            {
                a = null;
                int b = 1;
                b = 0;
            }
        }";
        var tree = SyntaxTree.ParseCompilationUnit(source);

        var mscorlib = new AssemblyFileReference(
            typeof(object).Assembly.Location);

        var comp = Compilation.Create(
            "MyCompilation",
            syntaxTrees: new[] { tree },
            references: new[] { mscorlib });

        var semanticModel = comp.GetSemanticModel(tree);

        var identifierNameSyntaxForA = tree.Root.FindToken(source.IndexOf("a = null")).Parent;
        var semanticInfo = semanticModel.GetSemanticInfo((ExpressionSyntax)identifierNameSyntaxForA);
        IsTypeString(semanticInfo.Type);

        var identifierNameSyntaxForB = tree.Root.FindToken(source.IndexOf("b = 0")).Parent;
        semanticInfo = semanticModel.GetSemanticInfo((ExpressionSyntax)identifierNameSyntaxForB);
        IsTypeString(semanticInfo.Type);
    }

    Hope this helps!!


    Shyam Namboodiripad | Software Development Engineer in Test | Roslyn Compilers Team
  • Friday, November 11, 2011 8:10 PM
     
     
    What if you are working with an ITypeSymbol? Do you guys preserve object identity for all symbols? Like reflection? So I can just compare the the object references? Even then all implementers would have to abide by the convention... What happens if I'm reflecting over C# code but referencing a VB assembly. Will types in that VB assembly come back as VisualBasic.TypeSymbols?
  • Monday, November 14, 2011 8:57 PM
     
     

    Hey Peter, supposing you wanted to add AQN to ITypeSymbol in a future release. How would you do? Is there a way to add a method to an interface and still be backwards compatible? As I understand interfaces, new members cannot be added after the first release without breaking everything that implemented the interface. Maybe the ISymbol hierarchy types should be a set of abstract types instead. What do you think? 

    Thanks,
    Chris 

  • Monday, November 14, 2011 9:54 PM
    Owner
     
     
    What if you are working with an ITypeSymbol? Do you guys preserve object identity for all symbols? Like reflection? So I can just compare the the object references? Even then all implementers would have to abide by the convention... What happens if I'm reflecting over C# code but referencing a VB assembly. Will types in that VB assembly come back as VisualBasic.TypeSymbols?


    Reference equality is not guaranteed for symbols. For example, if you get a symbol for a particular type from one compilation and a symbol for the same type from another compilation it is not guaranteed that you will get back the same object. It is not even guaranteed that equality comparison (.Equals() or ==) will work when you compare symbols that are obtained from different compilations - it may work in some cases if the compiler happens to reuse the symbol - but there is no guarantee.

    If you need to reliably compare types across compilations, then it is best to implement your own comparison for these based on your definition of equality. For example, for your app, two type symbols may only be ‘equal’ if they are coming from the same assembly and if they have the same fully qualified name – however, in other scenarios like multi-targeting you may consider symbols for types with the same fully qualified name from different assemblies ‘equal’...

    All symbols you get from C# compilations will be CSharp.Symbols regardless of whether the C# compilation is referencing a VB assembly. Similarly, all symbols you get from VB compilations will be VisualBasic.Symbols...


    Shyam Namboodiripad | Software Development Engineer in Test | Roslyn Compilers Team
  • Monday, November 14, 2011 10:14 PM
     
     

    Yes, that would potentially be a problem. We're looking at changing many of the "IXXX" interfaces to abstract classes for that reason. With Symbols, its not so simple because we already have a heirarchy between Symbol, TypeSymbol, NamedTypeSymbol, MethodSymbol and the like, and NamedTypeSymbol can't inherit both from INamedTypeSymbol and TypeSymbol.

    We need to think about this more.

  • Saturday, November 19, 2011 3:24 AM
     
      Has Code

    Hey Peter, I tried to load typeof(object) this way but had little luck.

                assembly = typeof(object).Assembly;
                var references = new [] { MetadataReference.Create(assembly.Location) };
                var compilation = Compilation.Create("LoaderContext", references: references);
                var type = compilation.GetTypeByMetadataName(typeof(object).AssemblyQualifiedName);
    

    In my test "type" came back null and I expected a Symbol. I expected Compilation to be a loader context against which AQNs could be resolved. Then a Roslyn Compilation could be used in place of Reflection when loading the target assemblies is undesirable. That would be cool.

    If I using "System.Object" in place of typeof(object).AssemblyQualifiedName then it works. If I declare a type System.Object in my LoaderContext assembly then GetTypeByMetadataName (arbitrarily?) binds to the source symbol instead of the BCL object. 

    Thanks,
    Chris 









    • Edited by kingces95 Saturday, November 19, 2011 4:20 AM
    •