47

How i can get full right name of generic type?

For example: This code

typeof(List<string>).Name

return

List`1

instead of

List<string>

How to get a right name?

typeof(List<string>).ToString()

returns System.Collections.Generic.List`1[System.String] but i want to get initial name:

List<string>

Is it real?

Neir0
  • 12,849
  • 28
  • 83
  • 139

10 Answers10

47

Use the FullName property.

typeof(List<string>).FullName

That will give you the namespace + class + type parameters.

What you are asking for is a C# specific syntax. As far as .NET is concerned, this is proper:

System.Collections.Generic.List`1[System.String]

So to get what you want, you'd have to write a function to build it the way you want it. Perhaps like so:

static string GetCSharpRepresentation( Type t, bool trimArgCount ) {
    if( t.IsGenericType ) {
        var genericArgs = t.GetGenericArguments().ToList();

        return GetCSharpRepresentation( t, trimArgCount, genericArgs );
    }

    return t.Name;
}

static string GetCSharpRepresentation( Type t, bool trimArgCount, List<Type> availableArguments ) {
    if( t.IsGenericType ) {
        string value = t.Name;
        if( trimArgCount && value.IndexOf("`") > -1 ) {
            value = value.Substring( 0, value.IndexOf( "`" ) );
        }

        if( t.DeclaringType != null ) {
            // This is a nested type, build the nesting type first
            value = GetCSharpRepresentation( t.DeclaringType, trimArgCount, availableArguments ) + "+" + value;
        }

        // Build the type arguments (if any)
        string argString = "";
        var thisTypeArgs = t.GetGenericArguments();
        for( int i = 0; i < thisTypeArgs.Length && availableArguments.Count > 0; i++ ) {
            if( i != 0 ) argString += ", ";

            argString += GetCSharpRepresentation( availableArguments[0], trimArgCount );
            availableArguments.RemoveAt( 0 );
        }

        // If there are type arguments, add them with < >
        if( argString.Length > 0 ) {
            value += "<" + argString + ">";
        }

        return value;
    }

    return t.Name;
}

For these types (with true as 2nd param):

typeof( List<string> ) )
typeof( List<Dictionary<int, string>> )

It returns:

List<String>
List<Dictionary<Int32, String>>

In general though, I'd bet you probably don't need to have the C# representation of your code and perhaps if you do, some format better than the C# syntax would be more appropriate.

Adam Sills
  • 16,896
  • 6
  • 51
  • 56
  • Output: System.Collections.Generic.List`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] – Hans Passant Apr 05 '10 at 17:25
  • Already updated nobugz (his clarifications weren't there originally) :) – Adam Sills Apr 05 '10 at 17:27
  • 2
    Adam, your code is incorrect. Try it with class C { public class D { } } and GetCSharpRepresentation(typeof(C.D)). You don't get the C# representation. – Eric Lippert Apr 05 '10 at 19:15
  • So just a nested type and a nested type where the base type has type arguments? Given an understanding of *why* he'd do this silly thing anyway, I don't doubt there's little legitimate use for this code in the first place, let alone a full functional "get c# representation of a type name" function. – Adam Sills Apr 05 '10 at 19:32
  • Yuck. Heh, for completeness' sake I went ahead and updated the sample to handle nested types (both generic and non-generic). It appears to work in all my scenarios. – Adam Sills Apr 05 '10 at 19:54
  • typeof( Stupid.Stupider ) gives me Stupid+Stupider and typeof( Dictionary.KeyCollection ) gives me Dictionary+KeyCollection – Adam Sills Apr 05 '10 at 19:56
  • Though using nested types seems to defeat the original desire, which is to match a C# type definition, which this certainly does not do (w/ nested types), since there is no succinct way to represent a nested type in C#. – Adam Sills Apr 05 '10 at 19:57
6

You could use this:

public static string GetTypeName(Type t) {
  if (!t.IsGenericType) return t.Name;
  if (t.IsNested && t.DeclaringType.IsGenericType) throw new NotImplementedException();
  string txt = t.Name.Substring(0, t.Name.IndexOf('`')) + "<";
  int cnt = 0;
  foreach (Type arg in t.GetGenericArguments()) {
    if (cnt > 0) txt += ", ";
    txt += GetTypeName(arg);
    cnt++;
  }
  return txt + ">";
}

For example:

static void Main(string[] args) {
  var obj = new Dictionary<string, Dictionary<HashSet<int>, int>>();
  string s = GetTypeName(obj.GetType());
  Console.WriteLine(s);
  Console.ReadLine();
}

Output:

Dictionary<String, Dictionary<HashSet<Int32>, Int32>>
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
2

If you have an instance of the list, you can call .ToString() and get the following

System.Collections.Generic.List`1[System.String]

This is in addition to the methods provided by the other answers directly against the type rather than the instance.

Edit: On your edit, I do not believe it is possible without providing your own parsing method, as List<string> is C# shorthand for how the type is implemented, sort of like if you wrote typeof(int).ToString(), what is captured is not "int" but the CTS name, System.Int32.

Anthony Pegram
  • 123,721
  • 27
  • 225
  • 246
2

Here's my implementation, which benefited from @Hans's answer above and @Jack's answer on a duplicate question.

public static string GetCSharpName( this Type type )
{
    string result;
    if ( primitiveTypes.TryGetValue( type, out result ) )
        return result;
    else
        result = type.Name.Replace( '+', '.' );

    if ( !type.IsGenericType )
        return result;
    else if ( type.IsNested && type.DeclaringType.IsGenericType )
        throw new NotImplementedException();

    result = result.Substring( 0, result.IndexOf( "`" ) );
    return result + "<" + string.Join( ", ", type.GetGenericArguments().Select( GetCSharpName ) ) + ">";
}

static Dictionary<Type, string> primitiveTypes = new Dictionary<Type, string>
{
    { typeof(bool), "bool" },
    { typeof(byte), "byte" },
    { typeof(char), "char" },
    { typeof(decimal), "decimal" },
    { typeof(double), "double" },
    { typeof(float), "float" },
    { typeof(int), "int" },
    { typeof(long), "long" },
    { typeof(sbyte), "sbyte" },
    { typeof(short), "short" },
    { typeof(string), "string" },
    { typeof(uint), "uint" },
    { typeof(ulong), "ulong" },
    { typeof(ushort), "ushort" },
};
Community
  • 1
  • 1
HappyNomad
  • 4,458
  • 4
  • 36
  • 55
2

Another way to get a nice type name by using an extension:

typeof(Dictionary<string, Dictionary<decimal, List<int>>>).CSharpName();
// output is: 
// Dictionary<String, Dictionary<Decimal, List<Int32>>>

The Extension Code:

public static class TypeExtensions
{
   public static string CSharpName(this Type type)
   {
       string typeName = type.Name;

       if (type.IsGenericType)
       {
           var genArgs = type.GetGenericArguments();

           if (genArgs.Count() > 0)
           {
               typeName = typeName.Substring(0, typeName.Length - 2);

               string args = "";

               foreach (var argType in genArgs)
               {
                   string argName = argType.Name;

                   if (argType.IsGenericType)
                       argName = argType.CSharpName();

                   args += argName + ", ";
               }

               typeName = string.Format("{0}<{1}>", typeName, args.Substring(0, args.Length - 2));
           }
       }

       return typeName;
   }        
}
stomtech
  • 454
  • 4
  • 10
1
typeof(List<string>).ToString()
wRAR
  • 25,009
  • 4
  • 84
  • 97
1

I had problems with the other answers in some instances, i.e. with arrays, so I ended up writing yet another one. I don't use the text from Type.Name or similar except to get the plain name of the types, because I don't know if the format is guaranteed to be the same across different .Net versions or with other implementations of the libraries (I assume it isn't).

/// <summary>
/// For the given type, returns its representation in C# code.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="genericArgs">Used internally, ignore.</param>
/// <param name="arrayBrackets">Used internally, ignore.</param>
/// <returns>The representation of the type in C# code.</returns>

public static string GetTypeCSharpRepresentation(Type type, Stack<Type> genericArgs = null, StringBuilder arrayBrackets = null)
{
    StringBuilder code = new StringBuilder();
    Type declaringType = type.DeclaringType;

    bool arrayBracketsWasNull = arrayBrackets == null;

    if (genericArgs == null)
        genericArgs = new Stack<Type>(type.GetGenericArguments());


    int currentTypeGenericArgsCount = genericArgs.Count;
    if (declaringType != null)
        currentTypeGenericArgsCount -= declaringType.GetGenericArguments().Length;

    Type[] currentTypeGenericArgs = new Type[currentTypeGenericArgsCount];
    for (int i = currentTypeGenericArgsCount - 1; i >= 0; i--)
        currentTypeGenericArgs[i] = genericArgs.Pop();


    if (declaringType != null)
        code.Append(GetTypeCSharpRepresentation(declaringType, genericArgs)).Append('.');


    if (type.IsArray)
    {
        if (arrayBrackets == null)
            arrayBrackets = new StringBuilder();

        arrayBrackets.Append('[');
        arrayBrackets.Append(',', type.GetArrayRank() - 1);
        arrayBrackets.Append(']');

        Type elementType = type.GetElementType();
        code.Insert(0, GetTypeCSharpRepresentation(elementType, arrayBrackets : arrayBrackets));
    }
    else
    {
        code.Append(new string(type.Name.TakeWhile(c => char.IsLetterOrDigit(c) || c == '_').ToArray()));

        if (currentTypeGenericArgsCount > 0)
        {
            code.Append('<');
            for (int i = 0;  i < currentTypeGenericArgsCount;  i++)
            {
                code.Append(GetTypeCSharpRepresentation(currentTypeGenericArgs[i]));
                if (i < currentTypeGenericArgsCount - 1)
                    code.Append(',');
            }
            code.Append('>');
        }

        if (declaringType == null  &&  !string.IsNullOrEmpty(type.Namespace))
        {
            code.Insert(0, '.').Insert(0, type.Namespace);
        }
    }


    if (arrayBracketsWasNull  &&  arrayBrackets != null)
        code.Append(arrayBrackets.ToString());


    return code.ToString();
}

I have tested it with crazy types like this, and so far it has worked perfectly:

class C
{
    public class D<D1, D2>
    {
        public class E
        {
            public class K<R1, R2, R3>
            {
                public class P<P1>
                {
                    public struct Q
                    {
                    }
                }
            }
        }
    }
}

type = typeof(List<Dictionary<string[], C.D<byte, short[,]>.E.K<List<int>[,][], Action<List<long[][][,]>[], double[][,]>, float>.P<string>.Q>>[][,][,,,][][,,]);

// Returns "System.Collections.Generic.List<System.Collections.Generic.Dictionary<System.String[],Test.Program.C.D<System.Byte,System.Int16[,]>.E.K<System.Collections.Generic.List<System.Int32>[,][],System.Action<System.Collections.Generic.List<System.Int64[][][,]>[],System.Double[][,]>,System.Single>.P<System.String>.Q>>[][,][,,,][][,,]":
GetTypeCSharpRepresentation(type);

There may still be some gotchas I didn't think about, but there's a known one: to retrieve the names, I only get characters that meet the condition char.IsLetterOrDigit(c) || c == '_' until one that doesn't is found, so any names of types that use allowed characters that don't meet the condition will fail.

Trisibo
  • 1,085
  • 1
  • 10
  • 19
1

Came across this and thought I'd share my own solution. It handles multiple generic arguments, nullables, jagged arrays, multidimensional arrays, combinations of jagged/multidimensional arrays, and any nesting combinations of any of the above. I use it mainly for logging so that it's easier to identify complicated types.

public static string GetGoodName(this Type type)
{
    var sb = new StringBuilder();

    void VisitType(Type inType)
    {
        if (inType.IsArray)
        {
            var rankDeclarations = new Queue<string>();
            Type elType = inType;

            do
            {
                rankDeclarations.Enqueue($"[{new string(',', elType.GetArrayRank() - 1)}]");
                elType = elType.GetElementType();
            } while (elType.IsArray);

            VisitType(elType);

            while (rankDeclarations.Count > 0)
            {
                sb.Append(rankDeclarations.Dequeue());
            }
        }
        else
        {
            if (inType.IsGenericType)
            {
                var isNullable = inType.IsNullable();
                var genargs = inType.GetGenericArguments().AsEnumerable();
                var numer = genargs.GetEnumerator();

                numer.MoveNext();

                if (!isNullable) sb.Append($"{inType.Name.Substring(0, inType.Name.IndexOf('`'))}<");

                VisitType(numer.Current);

                while (numer.MoveNext())
                {
                    sb.Append(",");
                    VisitType(numer.Current);
                }

                if (isNullable)
                {
                    sb.Append("?");
                }
                else
                {
                    sb.Append(">");
                }
            }
            else
            {
                sb.Append(inType.Name);
            }
        }
    }

    VisitType(type);

    var s = sb.ToString();

    return s;
}

This:

typeof(Dictionary<int?, Tuple<string[], List<string[][,,,]>>>).GetGoodName()

...returns this:

Dictionary<Int32?,Tuple<String[],List<String[][,,,]>>>
oscilatingcretin
  • 10,457
  • 39
  • 119
  • 206
1

An improvement on Adam Sills's answer that works with non-generic nested types, and generic type definitions:

public class TypeNameStringExtensions
{
    public static string GetCSharpRepresentation(Type t)
    {
        return GetCSharpRepresentation(t, new Queue<Type>(t.GetGenericArguments()));
    }
    static string GetCSharpRepresentation(Type t, Queue<Type> availableArguments)
    {
        string value = t.Name;
        if (t.IsGenericParameter)
        {
            return value;
        }
        if (t.DeclaringType != null)
        {
            // This is a nested type, build the parent type first
            value = GetCSharpRepresentation(t.DeclaringType, availableArguments) + "+" + value;
        }
        if (t.IsGenericType)
        {
            value = value.Split('`')[0];

            // Build the type arguments (if any)
            string argString = "";
            var thisTypeArgs = t.GetGenericArguments();
            for (int i = 0; i < thisTypeArgs.Length && availableArguments.Count > 0; i++)
            {
                if (i != 0) argString += ", ";

                argString += GetCSharpRepresentation(availableArguments.Dequeue());
            }

            // If there are type arguments, add them with < >
            if (argString.Length > 0)
            {
                value += "<" + argString + ">";
            }
        }

        return value;
    }

    [TestCase(typeof(List<string>), "List<String>")]
    [TestCase(typeof(List<Dictionary<int, string>>), "List<Dictionary<Int32, String>>")]
    [TestCase(typeof(Stupid<int>.Stupider<int>), "Stupid<Int32>+Stupider<Int32>")]
    [TestCase(typeof(Dictionary<int, string>.KeyCollection), "Dictionary<Int32, String>+KeyCollection")]
    [TestCase(typeof(Nullable<Point>), "Nullable<Point>")]
    [TestCase(typeof(Point?), "Nullable<Point>")]
    [TestCase(typeof(TypeNameStringExtensions), "TypeNameStringExtensions")]
    [TestCase(typeof(Another), "TypeNameStringExtensions+Another")]
    [TestCase(typeof(G<>), "TypeNameStringExtensions+G<T>")]
    [TestCase(typeof(G<string>), "TypeNameStringExtensions+G<String>")]
    [TestCase(typeof(G<Another>), "TypeNameStringExtensions+G<TypeNameStringExtensions+Another>")]
    [TestCase(typeof(H<,>), "TypeNameStringExtensions+H<T1, T2>")]
    [TestCase(typeof(H<string, Another>), "TypeNameStringExtensions+H<String, TypeNameStringExtensions+Another>")]
    [TestCase(typeof(Another.I<>), "TypeNameStringExtensions+Another+I<T3>")]
    [TestCase(typeof(Another.I<int>), "TypeNameStringExtensions+Another+I<Int32>")]
    [TestCase(typeof(G<>.Nested), "TypeNameStringExtensions+G<T>+Nested")]
    [TestCase(typeof(G<string>.Nested), "TypeNameStringExtensions+G<String>+Nested")]
    [TestCase(typeof(A<>.C<>), "TypeNameStringExtensions+A<B>+C<D>")]
    [TestCase(typeof(A<int>.C<string>), "TypeNameStringExtensions+A<Int32>+C<String>")]
    public void GetCSharpRepresentation_matches(Type type, string expected)
    {
        string actual = GetCSharpRepresentation(type);
        Assert.AreEqual(expected, actual);
    }

    public class G<T>
    {
        public class Nested { }
    }

    public class A<B>
    {
        public class C<D> { }
    }

    public class H<T1, T2> { }

    public class Another
    {
        public class I<T3> { }
    }
}

public class Stupid<T1>
{
    public class Stupider<T2>
    {
    }
}

I also chose to forgo his trimArgCount, as I can't see when that would be useful, and to use a Queue<Type> since that was the intent (pulling items off the front while they exist).

Community
  • 1
  • 1
Tom Mayfield
  • 6,235
  • 2
  • 32
  • 43
0

If you want the base generic type used:

List<string> lstString = new List<string>();
Type type = lstString.GetType().GetGenericTypeDefinition();

Assuming that you want to use the type do do something and that you don't really need the actual string definition which isn't all that useful.

Kelsey
  • 47,246
  • 16
  • 124
  • 162