views:

288

answers:

4

If I have this:

Type t = typeof(Dictionary<String, String>);

How do I get "System.Collections.Generic.Dictionary" as a string? Is the best/only way to do this:

String n = t.FullName.Substring(0, t.FullName.IndexOf("`"));

Seems kinda hackish to me though.

The reason I want this is that I want to take a Type object, and produce code that is similar to the one found in a C# source code file. I'm producing some text templates, and I need to add types as strings into the source, and the FullName property produces something like this:

System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089]]

instead of what I want:

System.Collections.Generic.Dictionary<System.String, System.String>


Edit: Ok, here's the final code, still seems a bit like a hack to me, but it works:

/// <summary>
/// This method takes a type and produces a proper full type name for it, expanding generics properly.
/// </summary>
/// <param name="type">
/// The type to produce the full type name for.
/// </param>
/// <returns>
/// The type name for <paramref name="type"/> as a string.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="type"/> is <c>null</c>.</para>
/// </exception>
public static String TypeToString(Type type)
{
    #region Parameter Validation

    if (Object.ReferenceEquals(null, type))
        throw new ArgumentNullException("type");

    #endregion

    if (type.IsGenericType)
    {
        if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            Type underlyingType = type.GetGenericArguments()[0];
            return String.Format("{0}?", TypeToString(underlyingType));
        }
        String baseName = type.FullName.Substring(0, type.FullName.IndexOf("`"));
        return baseName + "<" + String.Join(", ", (from paramType in type.GetGenericArguments()
                                                   select TypeToString(paramType)).ToArray()) + ">";
    }
    else
    {
        return type.FullName;
    }
}
+2  A: 

AFAIK the internal notation for generics uses the `x notation to indicate the number of type parameters to the generic type. I.e. 2 indicates that the Dictionary needs two types to close it. It is the same notation used by ILDasm and Reflection.

Brian Rasmussen
Correct, but is the only way to get the part of the name before the generic part by using Substring, as I showed, or is there a neater reflection-way, a property or method I don't know about?
Lasse V. Karlsen
As JohannesH points out in his answer, what you see is the IL notation. If you want something else (like C#) you need to map it yourself. We have a couple of in-house tools that do something similar to what you're suggesting. I am not aware of any other ways to get the C# syntax, but on the other hand I would assume the format to be fixed and thus you should be able to safely transform it yourself.
Brian Rasmussen
+5  A: 

The problem is that you want a language specific notation, in this case C#.

IL syntax:     [mscorlib]System.Collections.Generic.Dictionary`2<class [mscorlib]System.String, class [mscorlib]System.String>
C# syntax:     System.Collections.Generic.Dictionary<System.String, System.String>
VB.NET syntax: System.Collections.Generic.Dictionary(Of System.String, system.String)

Maybe you can call a language service to get the string you want, but you're probably better of with generating the string yourself.

JohannesH
Ok, I'll leave the code as it is then.
Lasse V. Karlsen
+2  A: 

You can use CodeDom to generate a more "normal" looking C#-style declaration.

CodeDomProvider csharpProvider = CodeDomProvider.CreateProvider("C#");
CodeTypeReference typeReference = new CodeTypeReference(typeof(Dictionary<string, int>));
CodeVariableDeclarationStatement variableDeclaration = new CodeVariableDeclarationStatement(typeReference, "dummy");
StringBuilder sb = new StringBuilder();
using (StringWriter writer = new StringWriter(sb))
{
    csharpProvider.GenerateCodeFromStatement(variableDeclaration, writer, new CodeGeneratorOptions());
}

sb.Replace(" dummy;", null);
Console.WriteLine(sb.ToString());

The above code has the following output:

System.Collections.Generic.Dictionary<string, int>

That should get you most of what you want without any custom type-stringifying code.

bobbymcr
+1  A: 

Is this closer to what you're looking for?

It uses CodeTypeReferenceExpression and doesn't require any further Substring or Replace calls:

var type = typeof(Dictionary<string, string>);
Console.WriteLine(TypeToString(type));

// ...

public static string TypeToString(Type type)
{
    if (type == null) throw new ArgumentNullException("type");

    var sb = new StringBuilder();
    using (var sw = new StringWriter(sb))
    {
        var expr = new CodeTypeReferenceExpression(type);

        var prov = new CSharpCodeProvider();
        prov.GenerateCodeFromExpression(expr, sw, new CodeGeneratorOptions());
    }
    return sb.ToString();
}
LukeH
-1: This is very much like http://stackoverflow.com/questions/1354265/correct-way-to-obtain-base-name-of-a-generic-type-in-net-is-through-substring/1354430#1354430
John Saunders
@John: You're right, but that doesn't mean this answer is wrong and/or deserves a downvote. Besides, the question asks how to do this without additional string manipulation, and bobbymcr's answer still requires a `Replace` call.
LukeH