Getting System.Type from a string using C# name resolution rules

Topics: APIs
Jul 16, 2014 at 10:47 PM
Edited Jul 16, 2014 at 10:51 PM
Hello,

In my application I have a string with some C# type, some references to external assemblies and namespaces in this assemblies, and I want to resolve this string to System.Type using standard C# name resolution rules.
For example, given a reference to "External.dll" and a namespace "ExternalNamespace" in this DLL, I want to convert:
  • "int" to System.Int32
  • "UserType" to ExternalNamespace.UserType (assuming one exists)
  • "Tuple<int, UserType>[]" to the corresponding array type
Currently I use the following code for it:
public static Type ResolveCSharpTypeName(string typename,
                                         IEnumerable<string> namespaces,
                                         Tuple<string, Type>[] typeAliases,
                                         IEnumerable<string> assemblyReferences) {
    var usingNamespaces = string.Join(Environment.NewLine, 
        namespaces.Select(n => "using {0};".FormatWith(n)));
    var usingAliases = string.Join(Environment.NewLine,
        typeAliases.Select(t => "using {0} = {1};".FormatWith(t.Item1, t.Item2.FullName)));
    string source = @"
        using System;
        using System.Text;
        using System.Text.RegularExpressions;
        using System.Collections.Generic;
        {1}{2}
        public static class C {{ public static Type F = typeof({0}); }}"
        .FormatWith(typename, usingNamespaces, usingAliases);
    var tree = SyntaxFactory.ParseSyntaxTree(source);
    MetadataReference[] references = 
        assemblyReferences.Select(s => new MetadataFileReference(s)).ToArray();
    var compilation = DefaultCompilation.AddReferences(references).AddSyntaxTrees(tree);
    using (var stream = new MemoryStream()) {
        var result = compilation.Emit(stream);
        if (!result.Success) return null;
        var assembly = Assembly.Load(stream.GetBuffer());
        return assembly.GetType("C").GetField("F").GetValue(null) as Type;
    }
}
It works, but emitting an entire assembly into a MemoryStream takes a lot of time. I suspect that all the required type references are already resolved when I create a compilation. Is this actually the case? If so, can I somehow get the required System.Type from a semantic model or a symbol in this model?

Thank you!
Jul 17, 2014 at 1:24 PM
It is certainly possible, but a little complicated. The debugger already does some of this, but most of it is internal (see SymbolDisplayCompilerInternalOptions).
First you get to the ITypeSymbol that represents your type:
string source = @"
using System;
using System.Collections.Generic;

public struct C
{
    public static Dictionary<int[,][], C?> F;
}";
var syntax = SyntaxFactory.ParseSyntaxTree(source);
var compilation = CSharpCompilation.Create("Blah").AddReferences(...).AddSyntaxTrees(syntax);
var fieldSymbol = compilation.GetTypeByMetadataName("C").GetMembers("F").First() as IFieldSymbol;
var typeSymbol = fieldSymbol.Type;
The main idea is to construct the IL metadata name from the type symbol which can be passed to Type.GetType(string). For this you can use SymbolVisitor as a base:
class MetadataDisplayVisitor : SymbolVisitor
{
    private StringBuilder _builder = new StringBuilder();
    public override string ToString()
    {
        return _builder.ToString();
    }
    public override void VisitNamespace(INamespaceSymbol symbol)
    {
        if( !symbol.IsGlobalNamespace )
        {
            base.Visit(symbol.ContainingNamespace);
            _builder.Append(symbol.MetadataName).Append('.');
        }
    }
    public override void VisitArrayType(IArrayTypeSymbol symbol)
    {
        base.Visit(symbol.ElementType);
        _builder.Append('[').Append(',', symbol.Rank - 1).Append(']');
    }
    public override void VisitPointerType(IPointerTypeSymbol symbol)
    {
        this.Visit(symbol.PointedAtType);
        _builder.Append('*');
    }
    public override void VisitDynamicType(IDynamicTypeSymbol symbol)
    {
        _builder.Append(typeof(object).FullName);
    }
    public override void VisitNamedType(INamedTypeSymbol symbol)
    {
        if( symbol.ContainingType != null )
        {
            base.Visit(symbol.ContainingType);
            _builder.Append('+');
        }
        else
        {
            base.Visit(symbol.ContainingNamespace);
        }

        _builder.Append(symbol.MetadataName);

        if( symbol.Arity > 0 )
        {
            _builder.Append('[');
            this.VisitTypeArgument(symbol.TypeArguments[0]);
            for( int i = 1; i < symbol.TypeArguments.Length; i++ )
            {
                _builder.Append(", ");
                this.VisitTypeArgument(symbol.TypeArguments[i]);
            }
            _builder.Append(']');
        }
    }
    private void VisitTypeArgument(ITypeSymbol symbol)
    {
        _builder.Append('[');
        base.Visit(symbol);
        this.AppendAssemblyName(symbol);
        _builder.Append(']');
    }
    private bool ShouldVisit(INamespaceSymbol symbol)
    {
        return symbol != null && !symbol.IsGlobalNamespace;
    }
    public void AppendAssemblyName(ITypeSymbol symbol)
    {
        switch( symbol.TypeKind )
        {
            case TypeKind.ArrayType:
                this.AppendAssemblyName((symbol as IArrayTypeSymbol).ElementType);
                break;
            case TypeKind.PointerType:
                this.AppendAssemblyName((symbol as IPointerTypeSymbol).PointedAtType);
                break;
            case TypeKind.DynamicType:
                _builder.Append(", ").Append(typeof(object).Assembly.FullName);
                break;
            default:
                _builder.Append(", ").Append(symbol.ContainingAssembly.Identity.GetDisplayName());
                break;
        }
    }
}
static class SymbolExtensions
{
    public static string ToMetadataName(this ITypeSymbol symbol)
    {
        var visitor = new MetadataDisplayVisitor();
        visitor.Visit(symbol);
        visitor.AppendAssemblyName(symbol);
        return visitor.ToString();
    }
}
And now you can simply use it like this:
var type = Type.GetType(typeSymbol.ToMetadataName());
For me this worked for pretty much any type I could throw at it, but no guarantees.
Jul 18, 2014 at 1:07 AM
Thank you BachratyGergely,

This is a great solution. The only case where it doesn't work is when the desired type is static (which is often the case in my application). In this case, there's no way to create an ITypeSymbol, because static classes cannot have instances. In fact, the only valid ways to refer to a static type are:
  • to plug it inside a typeof (as I did in my original solution)
  • to have it on the left-hand side of a static member reference T.Member
  • to use it on the right-hand side of a using type alias
The second option is inaccessible, because I don't know any static members of a desired type beforehand. So, is there any way to extract an ITypeSymbol out of a typeof expression or a using type alias expression?
Jul 18, 2014 at 9:54 AM
You have to walk the syntax tree for that and find the associated TypeSyntax. This is more or less trivial for the using syntax:
string source = @"using MyType = System.Collections.Generic.Dictionary<string, System.Guid?>;";
var syntax = SyntaxFactory.ParseSyntaxTree(source);
var typeSyntax = syntax.GetCompilationUnitRoot().Usings[0].Alias.Name;
However you may only use fully qualified type names here. The same is possible with a typeof() expression:
string source = @"using System; using System.Collections.Generic;
static class C { public static Type F = typeof(Dictionary<int[,][], Guid?>); }";

var syntax = SyntaxFactory.ParseSyntaxTree(source);
var typeOfSyntax = syntax.GetRoot().DescendantNodes().OfType<TypeOfExpressionSyntax>().First();
var typeSyntax = typeOfSyntax.Type;
You may want to walk the syntax tree manually instead of enumerating the entire syntax tree with DescendantNodes().
Finally you can get the ITypeSymbol from the TypeSyntax like this:
var compilation = ...
var typeSymbol = compilation.GetSemanticModel(syntax).GetTypeInfo(typeSyntax).Type;