开发者

What is the best way to get a proper type name from a C# keyword?

I want to create a type using Type.GetType( nameOfTheType ), where nameOfTheType is some string. If nameOfTheType is "System.String", this works great. If nameOfTh开发者_运维百科eType is "string", this throws an exception. All types with keyword shortcuts fail.

Is there a way, other than a big switch statement, to correctly map all of the types with keyword shortcuts to their actual type names (as per this list: http://msdn.microsoft.com/en-us/library/ya5y69ds%28VS.80%29.aspx)?

Edit: Gabriel has correctly pointed out that the MSDN page I linked does not include nullable types (int?, etc.), but it is important that nullable types are mapped correctly as well.


Disclaimer
Let me start out by saying that the method I'm going to demonstrate here is purely for educational purposes. Please use the switch or dictionary lookups demonstrated in the other answers in your production code.

Answer
So, the only way to find out how the compiler would interpret a given piece of code is to actually compile it and inspect the generated assembly. This isn't so hard to do in .NET... You could compile an arbitrary string during runtime using the C# compiler like so:

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

However, this technique has several drawbacks... First of all it is slow. Of course you could cache the result and do other trickery to speed up the process. But there's another problem, which is that it compiles and loads a complete assembly each time it reaches the inside of the "if" statement. Each assembly it loads cannot be unloaded again and will hang around until the application closes, so this technique will also leak a small amount of memory. Again, you could load the generated assembly into an separate AppDomain and unload the domain after you've inspected the type but that would only make the code a lot slower.

But as I pleaded in the beginning, just use the "switch" solution in your production code... It does a perfectly good job of translating the aliases. And it's not like they're going to change those language aliases anytime soon.


You could use the CodeDom to create a CodeVariableDeclarationStatement, and retrieve the Type property:

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

I am not sure whether you can use CodeDom classes that independently, but it should be an easy way to get from "string" to "System.String" without needing to create a lookup function using switch.

EDIT:

Thinking about it more, it may be possible to simply use a CodeTypeReference directly, and shortcut the process above:

var systemTypeName = new CodeTypeReference("string").BaseType;

The CodeVariableDeclarationStatement.Type property is a CodeTypeReference. By using CodeTypeReference directly, you don't need to bother with a dummy variable name, and it can become a one-liner.

WORKING EXAMPLE:

I found the code that did this previously. It is a bit more complicated than I had hoped, but it gets the job done, and if the generated classes are cached, performance is good after initial compilation:

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

The output of this app is as follows:

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


string, int, and every other keyword is just that: a keyword. It's merely a shortcut to writing String or Int32 or Double all the time. You would have to simply use their fully qualified names, but I don't know why you are opposed to simply using their full names, and not their keywords.

The only way I see of mapping keywords to fully qualified names would simply be a switch statement. There's no method that I'm aware of that will do what you're asking.


System.String is the type name, 'string' is an alias. The following code works for me, so perhaps I don't get your use case:

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

... which of course is completely redundant as you could just ask 's' for its type :)

EDIT: After seeing the comment below, you may just need to make a switch. The problem is that Type.GetType() wants a fully qualified name, and (after some searching) I could not find a way to match an alias to its type name. If someone does find that, awesome, but something like this would work well enough:

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


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


The Type name strings are originating from somewhere, and if that somewhere can be forced to create the Type name string in the following way using the .FullName property of the Type class, then you will not get the short form that you indicated is problematic:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜