13

I want to get a System.Type given a string that specifies a (primitive) type's C# friendly name, basically the way the C# compiler does when reading C# source code.

I feel the best way to describe what I'm after is in the form of an unit-test.

My hope is that a general technique exists that can make all the below assertions pass, rather than try to hard-code special cases for special C# names.

Type GetFriendlyType(string typeName){ ...??... }

void Test(){
    // using fluent assertions

    GetFriendlyType( "bool" ).Should().Be( typeof(bool) );
    GetFriendlyType( "int" ).Should().Be( typeof(int) );

    // ok, technically not a primitive type... (rolls eyes)
    GetFriendlyType( "string" ).Should().Be( typeof(string) ); 

    // fine, I give up!
    // I want all C# type-aliases to work, not just for primitives
    GetFriendlyType( "void" ).Should().Be( typeof(void) );
    GetFriendlyType( "decimal" ).Should().Be( typeof(decimal) ); 

    //Bonus points: get type of fully-specified CLR types
    GetFriendlyName( "System.Activator" ).Should().Be(typeof(System.Activator));

    //Hi, Eric Lippert! 
    // Not Eric? https://stackoverflow.com/a/4369889/11545
    GetFriendlyName( "int[]" ).Should().Be( typeof(int[]) ); 
    GetFriendlyName( "int[,]" ).Should().Be( typeof(int[,]) ); 
    //beating a dead horse
    GetFriendlyName( "int[,][,][][,][][]" ).Should().Be( typeof(int[,][,][][,][][]) ); 
}

What I tried so far:

This question is the complement of an older question of mine asking how to get the "friendly name" from a type.

The answer to that question is: use CSharpCodeProvider

using (var provider = new CSharpCodeProvider())
{
    var typeRef = new CodeTypeReference(typeof(int));
    string friendlyName = provider.GetTypeOutput(typeRef);
}

I can't figure out how (or if possible) to do it the other way around and get the actual C# type from the CodeTypeReference (it also has a ctor that takes a string)

var typeRef = new CodeTypeReference(typeof(int));
Community
  • 1
  • 1
Cristian Diaconescu
  • 34,633
  • 32
  • 143
  • 233
  • _"Here's a unit-test"_ - So, you want us to write the implementation? Show what you have tried. – CodeCaster Jun 07 '13 at 12:22
  • 7
    This was the simplest most straight-forward way to describe what I'm after. Not trying to be a smart-ass or anything. I thought fellow programmers would appreciate it. – Cristian Diaconescu Jun 07 '13 at 12:23
  • That's not the point. You only describe what you want, not what you have tried. If you want code delivery, call a developer and pay him. Here you're supposed to show research effort. @aquaraga I'm not asking what he's asking, as that itself is indeed pretty clear. – CodeCaster Jun 07 '13 at 12:24
  • 1
    @CodeCaster, he wants to get the 'Type' from the friendly name! And I donot agree with your point. This definitely cannot be categorized as a homework question or whatever. – aquaraga Jun 07 '13 at 12:25
  • Also, I could hack a solution, but look what happens when you do that and Eric Lippert catches you at it! http://stackoverflow.com/a/4369889/11545 -> look at the comments! I feel sorry for that guy. – Cristian Diaconescu Jun 07 '13 at 12:25
  • This question is related, though it might go the other way: http://stackoverflow.com/questions/4615553/c-sharp-get-user-friendly-name-of-simple-types-through-reflection – René Wolferink Jun 07 '13 at 12:28
  • 4
    @CodeCaster I'm not trying to get anybody to do my job for free. Answering is opt-in. My reasoning is: if I'm going to ask a question, and somebody may put effort in answering it, the least I can do is make it straight-forward and unambiguous, so that person spends the minimum amount of energy understanding what I'm after. – Cristian Diaconescu Jun 07 '13 at 12:29
  • @RenéWolferink Yes. I also asked that question. Look at the last line of *this* question (the one starting with "Hint:") - it's a link to that. – Cristian Diaconescu Jun 07 '13 at 12:30
  • why not use `Type.GetType()` with some *workaround* (like System.Int32->int, etc.) – Sinatr Jun 07 '13 at 12:31
  • @CristiDiaconescu Oops, you're right, missed that one... – René Wolferink Jun 07 '13 at 12:33
  • @sinatr Because http://stackoverflow.com/a/4369889/11545 When Eric Lippert says it'd take him half a day to *properly* implement something like that (taking a route similar to your suggestion), I believe him! – Cristian Diaconescu Jun 07 '13 at 12:33
  • 2
    You could simply hardcode a list of C# type aliases. These aliases are just a couple of keywords the C# spec defined, but .net doesn't know about them. – CodesInChaos Jun 07 '13 at 12:40
  • yea what @CodesInChaos said. its a c# compiler facility. – Daniel A. White Jun 07 '13 at 13:17
  • @DanielA.White Yes, and we have programmatic access to the c# compiler using e.g. `CodeDomProvider.CreateProvider("CSharp")` or some other compiler-as-a-service method – Cristian Diaconescu Jun 07 '13 at 13:26

5 Answers5

12

Don't you have most of if worked out already?

The following gives you all built-in C# types as per http://msdn.microsoft.com/en-us/library/ya5y69ds.aspx, plus void.

using Microsoft.CSharp;
using System;
using System.CodeDom;
using System.Reflection;

namespace CSTypeNames
{
    class Program
    {
        static void Main(string[] args)
        {
            // Resolve reference to mscorlib.
            // int is an arbitrarily chosen type in mscorlib
            var mscorlib = Assembly.GetAssembly(typeof(int));

            using (var provider = new CSharpCodeProvider())
            {
                foreach (var type in mscorlib.DefinedTypes)
                {
                    if (string.Equals(type.Namespace, "System"))
                    {
                        var typeRef = new CodeTypeReference(type);
                        var csTypeName = provider.GetTypeOutput(typeRef);

                        // Ignore qualified types.
                        if (csTypeName.IndexOf('.') == -1)
                        {
                            Console.WriteLine(csTypeName + " : " + type.FullName);
                        }
                    }
                }
            }

            Console.ReadLine();
        }
    }
}

This is based on several assumptions which I believe to be correct as at the time of writing:

  • All built-in C# types are part of mscorlib.dll.
  • All built-in C# types are aliases of types defined in the System namespace.
  • Only built-in C# types' names returned by the call to CSharpCodeProvider.GetTypeOutput do not have a single '.' in them.

Output:

object : System.Object
string : System.String
bool : System.Boolean
byte : System.Byte
char : System.Char
decimal : System.Decimal
double : System.Double
short : System.Int16
int : System.Int32
long : System.Int64
sbyte : System.SByte
float : System.Single
ushort : System.UInt16
uint : System.UInt32
ulong : System.UInt64
void : System.Void

Now I just have to sit and wait for Eric to come and tell me just how wrong I am. I have accepted my fate.

Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
Kirill Shlenskiy
  • 9,367
  • 27
  • 39
6

Alias names like 'int','bool' etc. are not part of .NET Framework. Internally they are converted into System.Int32, System.Boolean etc. Type.GetType("int") should return you null. Best way to appoach this is by having a dictionary to map alias with their type e.g.

        Dictionary<string, Type> PrimitiveTypes = new Dictionary<string, Type>();
        PrimitiveTypes.Add("int", typeof(int));
        PrimitiveTypes.Add("long", typeof(long));
        etc.etc..
Yogee
  • 1,412
  • 14
  • 22
5

Here's a way to do it by using Roslyn:

using System;
using System.Linq;
using Roslyn.Scripting.CSharp;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(GetType("int[,][,][][,][][]"));
            Console.WriteLine(GetType("Activator"));
            Console.WriteLine(GetType("List<int[,][,][][,][][]>"));
        }

        private static Type GetType(string type)
        {
            var engine = new ScriptEngine();
            new[] { "System" }
                .ToList().ForEach(r => engine.AddReference(r));
            new[] { "System", "System.Collections.Generic" }
                .ToList().ForEach(ns => engine.ImportNamespace(ns));
            return engine
                .CreateSession()
                .Execute<Type>("typeof(" + type + ")");
        }
    }
}
Alex Filipovici
  • 31,789
  • 6
  • 54
  • 78
3

Here's one way to do it:

public Type GetType(string friendlyName)
{
    var provider = new CSharpCodeProvider();

    var pars = new CompilerParameters
    {
        GenerateExecutable = false,
        GenerateInMemory = true
    };

    string code = "public class TypeFullNameGetter"
                + "{"
                + "     public override string ToString()"
                + "     {"
                + "         return typeof(" + friendlyName + ").FullName;"
                + "     }"
                + "}";

    var comp = provider.CompileAssemblyFromSource(pars, new[] { code });

    if (comp.Errors.Count > 0)
        return null;

    object fullNameGetter = comp.CompiledAssembly.CreateInstance("TypeFullNameGetter");
    string fullName = fullNameGetter.ToString();            
    return Type.GetType(fullName);
}

Then if you pass in "int", "int[]" etc you get the corresponding type back.

Dominic
  • 481
  • 2
  • 10
  • +1, this works, you can also include the proper `using` statements (like `using System.Collections.Generic;`) before you define the class, so you can pass in parameters like `GetType("List")`. – Alex Filipovici Jun 07 '13 at 13:43
  • +1 Nice, and using the built-in tools. Take a look at my answer too - can't help noticing that for this simple piece of code (basically executing `typeof(something)` ) the Roslyn or Mono compiler-as-a-service approaches are more straight-forward. – Cristian Diaconescu Jun 07 '13 at 14:32
  • Also, on closer inspection: why execute `typeof(type).FullName` and then `Type.GetType(fullName)`, isn't it easier to just return `typeof(type)` in the first place? – Cristian Diaconescu Jun 07 '13 at 14:33
  • 1
    Returning a string is easier because it can be done by overriding ToString(). If you return a Type you then need to use reflection to invoke the method. Or you could define an interface, which the dynamic code implements, but then the dynamic code needs to reference the assembly. – Dominic Jun 07 '13 at 14:51
  • Its cool - the only caveat is as the type information is evaluated during runtime, so its slower than using dictionary (map) (i.e. Yogee's answer). But, using map has the overhead of declaring the dictionary properly. so, depending on the needs either of them shoul work, i guess! – Dreamer Jun 08 '13 at 07:22
  • @Dominic ok, that makes sense. It seems to me that the CodeDOM approach, while built-in, is more cumbersome to use than Roslyn or Mono compiler-as-a-service, as they let you compile and execute (or should I say evaluate?) delegates and even expressions. No restrictions on returned types there. – Cristian Diaconescu Jun 13 '13 at 10:48
0

Here's my stab at it. I approached it using two similar libraries:

  • Mono C# compiler-as-a-service
  • MS Roslyn C# compiler

I think it's pretty clean and straightforward.

Here I'm using Mono's C# compiler-as-a-service, namely the Mono.CSharp.Evaluator class. (It's available as a Nuget package named, unsurprisingly, Mono.CSharp)

using Mono.CSharp;

    public static Type GetFriendlyType(string typeName)
    {
        //this class could use a default ctor with default sensible settings...
        var eval = new Mono.CSharp.Evaluator(new CompilerContext(
                                                 new CompilerSettings(),
                                                 new ConsoleReportPrinter()));

        //MAGIC! 
        object type = eval.Evaluate(string.Format("typeof({0});", typeName));

        return (Type)type;
    }

Up next: The counterpart from "the friends in building 41" aka Roslyn...

Later:

Roslyn is almost as easy to install - once you figure out what's what. I ended up using Nuget package "Roslyn.Compilers.CSharp" (Or get it as a VS add-in). Just be warned that Roslyn requires a .NET 4.5 project.

The code is even cleaner:

using Roslyn.Scripting.CSharp;

    public static Type GetFriendlyType(string typeName)
    {
        ScriptEngine engine = new ScriptEngine();
        var type = engine.CreateSession()
                         .Execute<Type>(string.Format("typeof({0})", typeName));
        return type;
    }
Cristian Diaconescu
  • 34,633
  • 32
  • 143
  • 233