Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Technique for generating valid type names with generic in Roslyn

I'm trying out a few possible techniques for generating a dynamic proxy of a C# interface at runtime. So far I've found Roslyn has taken me a fair away without too much friction, but I'm a bit stuck on dealing with generic types. In particular, getting type names to parse.

My basic workflow is:

  • Build the scaffolding for usings, a namespace and a class as a CompilationUnitSyntax
  • Inspect the interface getting proxied
  • For every method on the interface, use the MethodInfo to build a MethodDeclarationSyntax using SyntaxFactory.MethodDeclaration, with the goal to my new dynamic class

Here's an example of the issue I'm puzzling over. At this point it seems that I need to parse a string to get a TypeSyntax (in this case for the return type), and the only place I can take it is from methodInfo.ReturnType.Name:

var methodDecl = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName(methodInfo.ReturnType.Name), methodInfo.Name);

The problem is SyntaxFactory.ParseTypeName is expecting 'valid' C# syntax type declarations, for example List<string>, but accessing the Name or FullName properties are in the form:

{Name = "List`1" FullName =
"System.Collections.Generic.List`1[[UnitTests.SamplePoco, UnitTests,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"}   System.Type
{System.RuntimeType}

which obviously won't parse, with the backticks, lack of angle brackets etc.

Is there a better bridge between Reflection style classes (MethodInfo, Types) and Roslyn syntax units? I'm also trying out a pure reflection emit style solution as well, but wanted to see if I could get a Roslyn based one going here.

like image 981
eddie.sholl Avatar asked Jun 27 '15 16:06

eddie.sholl


2 Answers

I created this extension method to address the issue.

static class SyntaxExtensions
{
    /// <summary>
    /// Generates the type syntax.
    /// </summary>
    public static TypeSyntax AsTypeSyntax( this Type type )
    {
        string name = type.Name.Replace( '+', '.' );

        if ( type.IsGenericType ) {
            // Get the C# representation of the generic type minus its type arguments.
            name = name.Substring( 0, name.IndexOf( "`" ) );

            // Generate the name of the generic type.
            var genericArgs = type.GetGenericArguments();
            return SyntaxFactory.GenericName( SyntaxFactory.Identifier( name ),
                SyntaxFactory.TypeArgumentList( SyntaxFactory.SeparatedList( genericArgs.Select( AsTypeSyntax ) ) )
            );
        } else
            return SyntaxFactory.ParseTypeName( name );
    }
}
like image 98
HappyNomad Avatar answered Nov 13 '22 08:11

HappyNomad


In order to create a TypeSyntax from a generic type, then this static factory class might help. You will need to get the list of generic arguments for types you wish to generate, but the factory I post at the bottom (also available in this gist) helped me get instances of TypeSyntax relatively painlessly.

Example of how to use it:

// List<Dictionary<string, List<Type>>>
TypeSyntaxFactory.GetTypeSyntax(
    "List",
    TypeSyntaxFactory.GetTypeSyntax(
        "Dictionary",
        TypeSyntaxFactory.GetTypeSyntax(
            "string"
        ),
        TypeSyntaxFactory.GetTypeSyntax(
            "List",
            "Type"
        )
    )
)

I'm not sure what the best way would be to deal with the output from reflection, but you could probably just take the substring of the type identifier before the "`" symbol. In my IDE, this symbol isn't a valid character for type names, so it should be safe to assume that it is part of the reflection type output.

Finally, here is the copy of this gist

public static class TypeSyntaxFactory
{
    /// <summary>
    /// Used to generate a type without generic arguments
    /// </summary>
    /// <param name="identifier">The name of the type to be generated</param>
    /// <returns>An instance of TypeSyntax from the Roslyn Model</returns>
    public static TypeSyntax GetTypeSyntax(string identifier)
    {
        return
            SyntaxFactory.IdentifierName(
                SyntaxFactory.Identifier(identifier)
            );
    }

    /// <summary>
    /// Used to generate a type with generic arguments
    /// </summary>
    /// <param name="identifier">Name of the Generic Type</param>
    /// <param name="arguments">
    /// Types of the Generic Arguments, which must be basic identifiers
    /// </param>
    /// <returns>An instance of TypeSyntax from the Roslyn Model</returns>
    public static TypeSyntax GetTypeSyntax(string identifier, params string[] arguments)
    {
        return GetTypeSyntax(identifier, arguments.Select(GetTypeSyntax).ToArray());
    }

    /// <summary>
    /// Used to generate a type with generic arguments
    /// </summary>
    /// <param name="identifier">Name of the Generic Type</param>
    /// <param name="arguments">
    /// Types of the Generic Arguments, which themselves may be generic types
    /// </param>
    /// <returns>An instance of TypeSyntax from the Roslyn Model</returns>
    public static TypeSyntax GetTypeSyntax(string identifier, params TypeSyntax[] arguments)
    {
        return
            SyntaxFactory.GenericName(
                SyntaxFactory.Identifier(identifier),
                SyntaxFactory.TypeArgumentList(
                    SyntaxFactory.SeparatedList(
                        arguments.Select(
                            x =>
                            {
                                if(x is GenericNameSyntax)
                                {
                                    var gen_x = x as GenericNameSyntax;
                                    return
                                        GetTypeSyntax(
                                            gen_x.Identifier.ToString(),
                                            gen_x.TypeArgumentList.Arguments.ToArray()
                                        );
                                }
                                else
                                {
                                    return x;
                                }
                            }
                        )
                    )
                )
            );
    }
}
like image 35
Frank Bryce Avatar answered Nov 13 '22 09:11

Frank Bryce