将“C#友好类型”名称转换为实际类型:“int”=> typeof(int)

13
我想要根据指定(基元)类型的C#友好名称的字符串获得一个System.Type。这基本上是C#编译器读取C#源代码时所做的方式。我觉得最好的描述我所寻找的方法是通过单元测试来实现。我希望存在一种通用的技术,可以使下面所有断言都通过,而不是针对特殊的C#名称硬编码特殊情况。
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://dev59.com/DW855IYBdhLWcg3wYjWF#4369889
    GetFriendlyName( "int[]" ).Should().Be( typeof(int[]) ); 
    GetFriendlyName( "int[,]" ).Should().Be( typeof(int[,]) ); 
    //beating a dead horse
    GetFriendlyName( "int[,][,][][,][][]" ).Should().Be( typeof(int[,][,][][,][][]) ); 
}

我尝试过的方法:

这个问题是我之前一个旧问题的补充,那个问题询问如何从一个类型中获取“友好名称(friendly name)”。

那个问题的答案是:使用CSharpCodeProvider

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

我无法想象如何(或者是否可能)以相反的方式获取CodeTypeReference的实际C#类型(它还有一个以string为参数的构造函数)。

var typeRef = new CodeTypeReference(typeof(int));

这是一个单元测试 - 那么,你想让我们编写实现代码吗?请展示一下你已经尝试过的内容。 - CodeCaster
7
这是描述我所追求的最简单、最直接的方式。我并不试图表现聪明或其他什么。我以为其他程序员会欣赏这种描述。 - Cristian Diaconescu
1
@CodeCaster,他想从友好名称中获取“Type”!我不同意你的观点。这绝对不能被归类为作业问题或其他什么。 - aquaraga
4
@CodeCaster 我并不想让任何人免费为我工作。回答是自愿的。我的理由是:如果我要问一个问题,而有人可能会付出努力来回答它,我至少可以做到让问题直截了当、明确无误,这样那个人花在理解我的问题上的精力就最小化了。 - Cristian Diaconescu
2
你可以简单地硬编码一个C#类型别名列表。这些别名只是C#规范定义的一些关键字,但是.NET并不知道它们。 - CodesInChaos
显示剩余9条评论
5个回答

12

你难道不已经解决了大部分的问题吗?

下面列出所有内置的C#类型,参考自http://msdn.microsoft.com/en-us/library/ya5y69ds.aspx,此外还有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();
        }
    }
}

这是基于我在写作时认为是正确的几个假设:

  • 所有内置的 C# 类型都包含在 mscorlib.dll 中。
  • 所有内置的 C# 类型都是在 System 命名空间中定义的类型别名。
  • CSharpCodeProvider.GetTypeOutput 调用返回的仅有内置的 C# 类型名称不包含任何一个点号“.”。

输出:

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

现在我只需要坐下等待Eric来告诉我我有多么错误。我已经接受了我的命运。


+1 我喜欢这个 - 没有硬编码的字符串 :) 但是原始类型的数组怎么办?我为了完整性更新了问题。我一开始应该明确说明的,抱歉。 - Cristian Diaconescu
如果可以的话,我会再次为上一个评论点赞:) - Cristian Diaconescu

6

像'int','bool'等别名名称不是.NET Framework的一部分。 在内部,它们被转换为System.Int32,System.Boolean等。Type.GetType(“int”)应该返回null。 处理这个问题的最佳方法是使用字典将别名映射到它们的类型,例如:

        Dictionary<string, Type> PrimitiveTypes = new Dictionary<string, Type>();
        PrimitiveTypes.Add("int", typeof(int));
        PrimitiveTypes.Add("long", typeof(long));
        etc.etc..

5

以下是使用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 + ")");
        }
    }
}

我喜欢它!比我的Roslyn实现稍微冗长一些,但您也考虑了一些常见的命名空间。+1 - Cristian Diaconescu
1
顺便说一下,实际的“编译代码片段”可以写成一个简单的表达式:...Execute("typeof(" + type + ")" ); 不需要虚假类。而且,Execute()有一个重载的泛型版本,可以省去后面的转换。请看我的答案 :) - Cristian Diaconescu
好的,更新我的答案以适应语法糖 :) - Alex Filipovici
脚本引擎,boo。 - Kugel

3
以下是一种方法:

这里是一个实现的方式:

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);
}

如果传入“int”,“int []”等参数,您将得到相应的类型。


+1,这个可以工作。在定义类之前,您还可以包括适当的“using”语句(例如“using System.Collections.Generic;”),这样您就可以传递参数,如“GetType(“List<int>”)”。 - Alex Filipovici
+1 很好,使用内置工具。同时看一下我的答案——不禁注意到对于这个简单的代码片段(基本上执行 typeof(something) ),Roslyn 或 Mono 编译器作为服务的方法更加直接。 - Cristian Diaconescu
另外,仔细检查后:为什么要执行 typeof(type).FullName 然后再执行 Type.GetType(fullName),直接在第一次返回 typeof(type) 不是更简单吗? - Cristian Diaconescu
1
返回字符串更容易,因为可以通过覆盖ToString()方法来完成。如果返回一个Type,那么需要使用反射调用该方法。或者您可以定义一个接口,动态代码实现该接口,但是动态代码需要引用该程序集。 - Dominic
很酷 - 唯一的注意事项是由于类型信息在运行时评估,因此它比使用字典(map)慢(即Yogee的答案)。但是,使用map需要正确声明字典的开销。因此,根据需要选择其中之一应该可以工作,我猜! - Dreamer
@Dominic 好的,我明白了。在我看来,CodeDOM方法虽然内置,但使用起来比Roslyn或Mono编译器作为服务更加繁琐,因为它们可以让你编译和执行(或者说评估)委托甚至表达式。在那里没有返回类型的限制。 - Cristian Diaconescu

0

这是我的尝试。我使用了两个类似的库:

  • Mono C#编译器作为服务
  • MS Roslyn C#编译器

我认为它非常干净和直观。

在这里,我使用了Mono的C#编译器作为服务,即Mono.CSharp.Evaluator类。(它作为一个Nuget包命名为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;
    }

接下来:来自“41栋楼里的朋友们”即Roslyn的对应物...

稍后:

Roslyn几乎和安装一样容易-一旦你搞清楚了什么是什么。我最终使用了Nuget包“Roslyn.Compilers.CSharp”(或者作为VS插件获取)。只是要注意,Roslyn 需要一个.NET 4.5项目。

代码甚至更加简洁:

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;
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接