从C#关键字获取正确类型名称的最佳方法是什么?

6
我想使用Type.GetType(nameOfTheType)创建一个类型,其中nameOfTheType是一个字符串。如果nameOfTheType是"System.String",则可以正常工作。如果nameOfTheType是"string",则会抛出异常。所有关键字缩写的类型都失败了。
除了使用一个大的switch语句之外,是否有其他方法可以正确映射所有带有关键字缩写的类型到它们实际的类型名称(根据此列表:http://msdn.microsoft.com/en-us/library/ya5y69ds%28VS.80%29.aspx)?
编辑:Gabriel正确指出我链接的MSDN页面不包括可空类型(int?等),但重要的是可空类型也要正确映射。

2
你想让它也适用于可空类型吗?例如 int?,double? 等。 - Gabriel
6个回答

5

免责声明
首先,我要说的是,我将在这里展示的方法纯粹是为了教育目的。请在生产代码中使用其他答案中演示的开关或字典查找。

答案
因此,找出编译器如何解释给定的代码的唯一方法是实际编译它并检查生成的汇编代码。在.NET中,这并不难...您可以使用C#编译器在运行时编译任意字符串,如下所示:

private static Type GetTypeByFullNameOrAlias(string typeName)
{
  Type type = Type.GetType(typeName);

  if (type == null)
  {
    using (var provider = new CSharpCodeProvider())
    {
      var compiler = provider.CompileAssemblyFromSource(
        new CompilerParameters
          {GenerateInMemory = true, GenerateExecutable = false, IncludeDebugInformation = false},
        "public class A { public " + typeName + " B; }");

      type = ((FieldInfo)compiler.CompiledAssembly.GetType("A").GetMember("B")[0]).FieldType;
    }
  }

  return type;
}

然而,这种技术有几个缺点...首先,它很慢。当然,你可以缓存结果并进行其他技巧来加速过程。但还有另一个问题,就是每次到达“if”语句的内部时,它都会编译和加载完整的程序集。它加载的每个程序集都无法再次卸载,并且将一直存在,直到应用程序关闭,因此这种技术也将泄漏少量的内存。同样,您可以将生成的程序集加载到单独的AppDomain中,并在检查类型后卸载该域,但这只会使代码变得更慢。

但正如我在开头所说,请在生产代码中使用“switch”解决方案...它完美地完成了别名的翻译工作。而且他们不会很快更改那些语言别名。


我不知道为什么人们会如此关注我的性能问题,尤其是在他们不了解上下文的情况下,但这是最好的答案。可空类型几乎使类型别名数量翻了一倍,而这段代码将经受住这样的考验,而 switch 语句则不行。谢谢。 - Greg Smalter
对于那些感兴趣的人,为了获得typeAsString -> typeObject -> typeAsString的正确往返,您需要执行以下操作:provider.GetTypeOutput(new CodeTypeReference(type))否则,可空类型(例如int?)会尝试转换为“System.Nullable`1”。 - Greg Smalter
我真的希望像这样的东西永远不要进入生产代码。 - ChaosPandion
@ChaosPandion: 我同意...个人而言,我会使用switch或你的字典解决方案。然而,在一些罕见的情况下,我展示的技巧可能非常有用。而且,这个解决方案也是唯一一个真正回答了问题“除了switch还有其他方法吗”。 ;) - JohannesH

4
你可以使用 CodeDom 来创建 CodeVariableDeclarationStatement,并检索 Type 属性:
var stmt = new CodeVariableDeclarationStatement("string", "test");
string systemTypeName = stmt.Type.BaseType;

我不确定你是否可以单独使用CodeDom类,但这应该是一种简单的方法,可以从“string”转换为“System.String”,而无需使用switch创建查找函数。
编辑:
思考更多后,可能可以直接使用CodeTypeReference来简化上述过程。
var systemTypeName = new CodeTypeReference("string").BaseType;

CodeVariableDeclarationStatement.Type属性是CodeTypeReference。通过直接使用CodeTypeReference,您无需费心设置虚拟变量名称,它可以变成一行代码。

工作示例:

我找到了以前执行此操作的代码。它比我希望的要复杂一些,但它完成了工作,如果生成的类被缓存,则初始编译后性能良好:

using System;
using System.CodeDom;
using System.Collections.Generic;
using System.CodeDom.Compiler;

using Microsoft.CSharp;
using System.Diagnostics;

namespace ConsoleApplication1
{
    public interface IDynamicTypeNameMapper
    {
        string GetTypeName();
    }

    class Program
    {
        static readonly string[] csharpKeywords = new[]
        {
            "byte",
            "short",
            "int",
            "long",
            "float",
            "double",
            "string"
        };

        static Dictionary<string, IDynamicTypeNameMapper> s_mappers;

        static void Main(string[] args)
        {
            s_mappers = new Dictionary<string, IDynamicTypeNameMapper>();

            var provider = new CSharpCodeProvider();
            var options = new CompilerParameters();
            options.ReferencedAssemblies.Add("ConsoleApplication1.exe");
            options.GenerateInMemory = true;

            var stopwatch = new Stopwatch();
            stopwatch.Start();
            foreach (string keyword in csharpKeywords)
            {
                string className = "DynamicTypeNameMapper_" + keyword;
                string literal = "using System; using ConsoleApplication1; namespace Test { public class " + className + ": IDynamicTypeNameMapper { public string GetTypeName() { return typeof(" + keyword + ").FullName; } } }";
                var snippet = new CodeSnippetCompileUnit(literal);

                var results = provider.CompileAssemblyFromDom(options, snippet);

                var typeNameMapper = results.CompiledAssembly.CreateInstance("Test." + className) as IDynamicTypeNameMapper;
                if (typeNameMapper != null)
                {
                    s_mappers.Add(keyword, typeNameMapper);
                    Console.WriteLine(typeNameMapper.GetTypeName());
                }
            }
            stopwatch.Stop();
            Console.WriteLine("Inital time: " + stopwatch.Elapsed.ToString());

            stopwatch.Reset();
            stopwatch.Start();

            for (int i = 0; i < 1000; i++)
            {
                foreach (string keyword in csharpKeywords)
                {
                    s_mappers[keyword].GetTypeName();
                }
            }
            stopwatch.Stop();

            Console.WriteLine("Cached time: " + stopwatch.Elapsed.ToString());

            Console.ReadLine();
        }
    }
}

这个应用程序的输出如下:
System.Byte
System.Int16
System.Int32
System.Int64
System.Single
System.Double
System.String
Inital time: 00:00:00.3090559
Cached time: 00:00:00.0011934

非常短小精悍。用户不必维护类型的switch语句,如果微软因任何原因扩展C#的数据类型,它仍将自动工作。 - John K
1
这似乎不起作用:在.NET 3.5上,无论哪种情况,systemTypeName都只评估为“string”。话虽如此,应该有一种方法可以使用CodeDom使其工作,但您可能需要使用CSharpCodeProvider,因为运行时仅支持IL。 - Gabriel
嗯,我目前遇到了一些硬盘问题。一有机会,我就会尝试找出之前是怎么做的。这应该是可能的,但可能没有我想象中那么简单。:\ - jrista
我已经添加了一个工作示例,包括计时。 初始编译需要0.3秒来处理所有七个C#关键字的示例,并且使用生成的类的缓存实例进行1000次完整类型名称的检索仅需要0.001秒。 一旦缓存,执行速度比最小的初始访问快得多,就像您提前编写并编译了代码一样执行。 - jrista
1
@Ray Burns:我必须反驳“极其”低效的说法。看看我提供的工作示例,并查看时间。初始编译虽然有一定的影响,但对于所有七个测试关键字来说,仍然只是一小部分。即使假设有几十个值类型关键字,我们也只需要一秒钟来编译它们一次,之后每个类型名称评估器都在正常的编译时间内执行。可以编写一个简单的实用程序类来支持轻松映射来自任何.NET语言的类型名称,提供动态的、未来的解决方案。 - jrista
显示剩余7条评论

2

System.String是类型名称,“string”是别名。以下代码对我有效,因此可能我不了解您的用例:

string s = "";
Type t = Type.GetType( s.GetType( ).ToString( ) );
Console.WriteLine( t );  // prints "System.String"

... 这当然是多余的,因为你可以直接询问's'的类型 :)

编辑: 在看到下面的评论后,您可能只需要进行一次切换。问题在于Type.GetType()需要一个完全限定名称,而(经过一些搜索)我找不到将别名与其类型名称匹配的方法。如果有人找到了,那太棒了,但是类似这样的东西就足够好了:

switch (userType)
{
    case "string": return "System.String";
    case "sbyte": return "System.SByte";
    case "byte": return "System.Byte";
    case "short": return "System.Int16";
    case "ushort": return "System.UInt16";
    case "int": return "System.Int32";
    case "uint": return "System.UInt32";
    case "long": return "System.Int64";
    case "ulong": return "System.UInt64";
    case "char": return "System.Char";
    case "float": return "System.Single";
    case "double": return "System.Double";
    case "bool": return "System.Boolean";
    case "decimal": return "System.Decimal";
    case "void": return "System.Void";
    case "object": return "System.Object";
    default: return userType;
}

我认为他想要根据文本“string”或“int”获取typeof(System.String)和typeof(System.Int32)。也就是说,他不一定有一个字符串或整数,而是有一个名称为“string”或“int”的变量名。 - itowlson
1
但那永远不会起作用,因为您需要传递完全限定名称。 - Ed S.
你的 case "int" 缺少一个闭合引号,你可以删除多余的 return "void" 并利用 fall-through。此外,我不知道性能会受到什么影响(我认为会受到影响),但使用类似于 Double.GetType().ToString() 的东西可能更优雅和抗 bug。但这只是个人偏好,以我个人的看法。 - user153498
我发现了那段代码,我只是在每个 case 中进行了交换(原本它返回别名的 clr 类型,与 OP 想要的相反)。 - Ed S.

2
static readonly Dictionary<String, String> types = new Dictionary<String, String>()
{
    { "string", "System.String" },
    { "sbyte", "System.SByte" },
    { "byte", "System.Byte" },
    { "short", "System.Int16" },
    { "ushort", "System.UInt16" },
    { "int", "System.Int32" },
    { "uint", "System.UInt32" },
    { "long", "System.Int64" },
    { "ulong", "System.UInt64" },
    { "char", "System.Char" },
    { "float", "System.Single" },
    { "double", "System.Double" },
    { "bool", "System.Boolean" },
    { "decimal", "System.Decimal" },
    { "void", "System.Void" },
    { "object", "System.Object" }
};

private void Execute(String user_type)
{
    String type;
    if (!types.TryGetValue(user_type, out type))
    {
        type = user_type;
    }
}

2
、和其他关键字只是一个快捷方式,用于代替一直写StringInt32Double。你也可以使用它们的完全限定名称,但我不知道为什么你反对使用它们的全名而不是关键字。

我所知道的唯一将关键字映射到完全限定名称的方法就是使用switch语句。没有其他的方法可以实现你所要求的功能。


如果你确实需要这个功能,似乎使用switch语句并且已经存在映射关系并不会太糟糕。这将是最清晰的方法(也可能是唯一的方法)。+1 - jheddings
我不反对使用它们的全名。但是,我不能选择传递给nameOfTheType的内容。我只需要它能够正常工作。 - Greg Smalter

-1

类型名称字符串来自某个地方,如果可以强制该位置使用Type类的.FullName属性以以下方式创建类型名称字符串,则不会出现您指出的问题:

string obj = ""; // using obj as an example - could be of any data type
string bestTypeName = obj.GetType().FullName; // produces "System.String"

你不必这样做,传递 obj.GetType.ToString() 将产生相同的效果。用户想要获取与 System.Int32 对应的 Type 对象,以便将字符串文字 "int" 转换为该对象。我曾经有过同样的困惑。 - Ed S.
除了用户询问是否“有更好的方法将所有这些东西放在一起”,这就是我选择回答的方式。 - John K
但这并没有解决问题。当然,"".GetType.FullName可以给出正确的名称,但是OP没有一个类型对象可以使用,他们只有一个字符串。 - Ed S.
@Ed Swangren:我没有表明OP有一个Type对象可供使用,因此我没有为其辩护的用处。相反,我谈到了字符串“来自某个地方”。当被邀请“一起找到更好的方法”时,我关注问题起源可能存在的问题。 - John K
我的解决方案只在我回复作者邀请时所概述的情况下有用。换句话说,如果作者没有邀请其他解决方案,我就没有这种灵活性。我在我的帖子中没有提到任何与你的四条评论不同的内容,并且没有超出作者要求的范围。很抱歉让你感到沮丧。我也感到有些沮丧,因为一遍又一遍地重申同样的事情! - John K
显示剩余2条评论

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