【问题标题】:What is the best way to get a proper type name from a C# keyword?从 C# 关键字中获取正确类型名称的最佳方法是什么?
【发布时间】:2009-11-03 00:36:16
【问题描述】:

我想使用 Type.GetType(nameOfTheType) 创建一个类型,其中 nameOfTheType 是一些字符串。如果 nameOfTheType 是“System.String”,这很好用。如果 nameOfTheType 是“字符串”,则会引发异常。所有带有关键字快捷方式的类型都会失败。

除了大的 switch 语句之外,还有其他方法可以将所有类型与关键字快捷方式正确映射到它们的实际类型名称(根据此列表:http://msdn.microsoft.com/en-us/library/ya5y69ds%28VS.80%29.aspx)?

编辑:Gabriel 已经正确指出我链接的 MSDN 页面不包括可为空的类型(int? 等),但重要的是也正确地映射了可为空的类型。

【问题讨论】:

  • 你希望它也适用于可空对象吗?例如。诠释? , 双倍的?等等

标签: c#


【解决方案1】:
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】:

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

    回答
    因此,找出编译器如何解释给定代码的唯一方法是实际编译它并检查生成的程序集。这在 .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 语句则不会。谢谢。
    • 对于那些感兴趣的人,为了获得正确的 typeAsString -> typeObject -> typeAsString 往返,您需要这样做: provider.GetTypeOutput( new CodeTypeReference( type ) ) 否则,可为空的类型,如作为整数?尝试变成“System.Nullable`1”。
    • 我真的希望这样的东西永远不会到达生产代码。
    • @ChaosPandion:我同意...我个人会使用开关或您的字典解决方案。但是,我展示的技术在极少数情况下可能非常有用。而且这个解决方案也是唯一真正回答“除了开关之外还有其他方法”的问题的解决方案。 ;)
    【解决方案3】:

    您可以使用CodeDom 创建CodeVariableDeclarationStatement,并检索Type 属性:

    var stmt = new CodeVariableDeclarationStatement("string", "test");
    string systemTypeName = stmt.Type.BaseType;
    

    我不确定你是否可以独立使用 CodeDom 类,但它应该是一种从“字符串”到“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 语句,如果 Microsoft 出于任何原因扩展 C# 的数据类型,它仍然会自动工作。
    • 这似乎不起作用:在这两种情况下,systemTypeName 在 .NET 3.5 上的计算结果都只是“字符串”。也就是说,应该有一种方法可以使用 CodeDom,但您可能需要使用 CSharpCodeProvider,因为运行时只说 IL。
    • 这更符合我正在寻找的内容。但是,正如加布里埃尔所说,它似乎不起作用。太糟糕了,没有办法告诉框架评估别名,因为关键字只是完全限定类型的别名。
    • 我之前使用 CodeDom 来获取类型的 .NET 系统名称。 BaseType 属性可能是不正确的属性。尝试使用 CodeTypeReferenceOptions.GlobalReference ...我认为这将迫使它为您映射系统类型。
    • @Ray Burns:我不得不对“极其”低效的言论提出异议。看看我提供的工作示例,看看你自己的时间安排。最初的编译虽然成功,但对于所有七个测试关键字来说仍然只有几分之一秒。即使假设有几十个值类型关键字,也要花一秒钟时间编译它们一次,然后每个类型名称评估器都会在正常的编译时间内执行。可以编写一个简单的实用程序类来轻松支持任何 .NET 语言的映射类型名称,从而提供动态的、面向未来的解决方案。
    【解决方案4】:

    stringint 和其他所有关键字都只是:关键字。这只是一直写StringInt32Double 的捷径。您将不得不简单地使用他们的全名,但我不知道您为什么反对简单地使用他们的全名,而不是他们的关键字。

    我看到的将关键字映射到完全限定名称的唯一方法就是使用switch 语句。我知道没有任何方法可以满足您的要求。

    【讨论】:

    • 如果你真的需要这个功能,看起来switch 声明不会那么糟糕,因为映射已经存在。这将是最干净的方法(也许是唯一的)。 +1
    • 我不反对使用他们的全名。但是,我无法选择为 nameOfTheType 传入的内容。我只需要它工作。
    【解决方案5】:

    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)。 IE。他不一定有 a 字符串或 int,他有 name "string" 或 "int"。
    • 但这永远行不通,因为您需要传入完全限定的名称。
    • 您的case "int" 缺少结束引号,您可以删除多余的return "void" 并利用掉线。另外,我不知道性能会受到怎样的影响(我会假设它会受到影响),但使用类似Double.GetType().ToString() 的东西可能会更优雅和抗错误。但这是个人喜好,IMO。
    • 我找到了那个代码,我只是切换了每个案例(它从别名返回 clr 类型,与 OP 想要的相反)。
    【解决方案6】:

    类型名称字符串源自某处,如果可以使用 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() 将具有相同的效果。用户想要获取字符串文字“int”并获取对应于 System.Int32 的 Type 对象。我也有同样的困惑。
    • 除了用户问是否有“更好的方法来一起完成这一切”,这就是我选择的回应方式。
    • 但这并没有解决问题。当然 "".GetType.FullName 给出了正确的名称,但是 OP 没有可以使用的类型对象,它们有一个字符串。
    • 这样说;如果 OP 已经拥有该对象,他为什么需要使用 Type.GetType()?他可以简单地在对象本身上调用 GetType(),所以这是没有意义的。
    • @Ed Swangren:我没有指出 OP 有一个可以使用的 Type 对象,所以我为它辩护是没有用的。相反,我谈到了“源自某处”的字符串。当受邀提出“一种更好的方式来共同完成这一切”时,我专注于问题根源的潜在问题。
    猜你喜欢
    • 1970-01-01
    • 2010-09-13
    • 2017-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-13
    • 1970-01-01
    • 2014-10-24
    相关资源
    最近更新 更多