The name properties of System.Type class return a strange result in case of generic types. Is there a way to get the type name in a format closer to the way I specified it?
Example: typeof(List<string>).OriginalName == "List<string>"
I understood, that I have to write this myself. Here is my solution (it is actually a bit more than asked for). It is, probably, helpfull.
using System.Reflection;
using HWClassLibrary.Debug;
using System.Collections.Generic;
using System.Linq;
using System;
namespace HWClassLibrary.Helper
{
public static class TypeNameExtender
{
private static IEnumerable<Type> _referencedTypesCache;
public static void OnModuleLoaded() { _referencedTypesCache = null; }
public static string PrettyName(this Type type)
{
if(type == typeof(int))
return "int";
if(type == typeof(string))
return "string";
var result = PrettyTypeName(type);
if(type.IsGenericType)
result = result + PrettyNameForGeneric(type.GetGenericArguments());
return result;
}
private static string PrettyTypeName(Type type)
{
var result = type.Name;
if(type.IsGenericType)
result = result.Remove(result.IndexOf('`'));
if (type.IsNested && !type.IsGenericParameter)
return type.DeclaringType.PrettyName() + "." + result;
if(type.Namespace == null)
return result;
var conflictingTypes = ReferencedTypes
.Where(definedType => definedType.Name == type.Name && definedType.Namespace != type.Namespace)
.ToArray();
var namespaceParts = type.Namespace.Split('.').Reverse().ToArray();
var namespacePart = "";
for(var i = 0; i < namespaceParts.Length && conflictingTypes.Length > 0; i++)
{
namespacePart = namespaceParts[i] + "." + namespacePart;
conflictingTypes = conflictingTypes
.Where(conflictingType => (conflictingType.Namespace + ".").EndsWith(namespacePart))
.ToArray();
}
return namespacePart + result;
}
private static IEnumerable<Type> ReferencedTypes
{
get
{
if(_referencedTypesCache == null)
_referencedTypesCache = Assembly.GetEntryAssembly().GetReferencedTypes();
return _referencedTypesCache;
}
}
private static string PrettyNameForGeneric(Type[] types)
{
var result = "";
var delim = "<";
foreach(var t in types)
{
result += delim;
delim = ",";
result += t.PrettyName();
}
return result + ">";
}
}
}
The problem with "pretty" names is they are different depending on the language you are using. Imagine the surprise of a VB.NET developer if OriginalName
returned C# syntax.
However, it's pretty fairly easy to make this yourself:
private static string PrettyName(Type type)
{
if (type.GetGenericArguments().Length == 0)
{
return type.Name;
}
var genericArguments = type.GetGenericArguments();
var typeDefinition = type.Name;
var unmangledName = typeDefinition.Substring(0, typeDefinition.IndexOf("`"));
return unmangledName + "<" + String.Join(",", genericArguments.Select(PrettyName)) + ">";
}
This will recursively resolve the unmanaged name, so that if you have something like Dictionary<string, IList<string>>
it should still work.
Dictionary<string,IList<string>>.KeyCollection
. Its CLR type name is Dictionary`2+KeyCollection[System.String,IList`1[System.String]]
(give or take namespace qualifiers). Furthermore you can have things like Outer`1+Inner`1[OuterArg,InnerArg]
. And of course you can have arrays (1D 2D etc.) and pointers to a generic type, which need handling. :) –
Selfdeceit I used CodeDomProvider
to convert to c#:
public static string GetOriginalName(this Type type)
{
string TypeName = type.FullName.Replace(type.Namespace + ".", "");//Removing the namespace
var provider = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("CSharp"); //You can also use "VisualBasic"
var reference = new System.CodeDom.CodeTypeReference(TypeName);
return provider.GetTypeOutput(reference);
}
Like Harold Hoyer's answer but including nullables and a few more built-in types:
/// <summary>
/// Get full type name with full namespace names
/// </summary>
/// <param name="type">
/// The type to get the C# name for (may be a generic type or a nullable type).
/// </param>
/// <returns>
/// Full type name, fully qualified namespaces
/// </returns>
public static string CSharpName(this Type type)
{
Type nullableType = Nullable.GetUnderlyingType(type);
string nullableText;
if (nullableType != null)
{
type = nullableType;
nullableText = "?";
}
else
{
nullableText = string.Empty;
}
if (type.IsGenericType)
{
return string.Format(
"{0}<{1}>{2}",
type.Name.Substring(0, type.Name.IndexOf('`')),
string.Join(", ", type.GetGenericArguments().Select(ga => ga.CSharpName())),
nullableText);
}
switch (type.Name)
{
case "String":
return "string";
case "Int32":
return "int" + nullableText;
case "Decimal":
return "decimal" + nullableText;
case "Object":
return "object" + nullableText;
case "Void":
return "void" + nullableText;
default:
return (string.IsNullOrWhiteSpace(type.FullName) ? type.Name : type.FullName) + nullableText;
}
}
Here is my implementation. It was created to describe methods, so it handles the ref
and out
keywords.
private static Dictionary<Type, string> shorthandMap = new Dictionary<Type, string>
{
{ typeof(Boolean), "bool" },
{ typeof(Byte), "byte" },
{ typeof(Char), "char" },
{ typeof(Decimal), "decimal" },
{ typeof(Double), "double" },
{ typeof(Single), "float" },
{ typeof(Int32), "int" },
{ typeof(Int64), "long" },
{ typeof(SByte), "sbyte" },
{ typeof(Int16), "short" },
{ typeof(String), "string" },
{ typeof(UInt32), "uint" },
{ typeof(UInt64), "ulong" },
{ typeof(UInt16), "ushort" },
};
private static string CSharpTypeName(Type type, bool isOut = false)
{
if (type.IsByRef)
{
return String.Format("{0} {1}", isOut ? "out" : "ref", CSharpTypeName(type.GetElementType()));
}
if (type.IsGenericType)
{
if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return String.Format("{0}?", CSharpTypeName(Nullable.GetUnderlyingType(type)));
}
else
{
return String.Format("{0}<{1}>", type.Name.Split('`')[0],
String.Join(", ", type.GenericTypeArguments.Select(a => CSharpTypeName(a)).ToArray()));
}
}
if (type.IsArray)
{
return String.Format("{0}[]", CSharpTypeName(type.GetElementType()));
}
return shorthandMap.ContainsKey(type) ? shorthandMap[type] : type.Name;
}
The calling code looks like this:
string line = String.Format("{0}.{1}({2})",
method.DeclaringType.Name,
method.Name,
String.Join(", ", method.GetParameters().Select(p => CSharpTypeName(p.ParameterType, p.IsOut) + " " + p.Name).ToArray()));
Where method
is a MethodInfo
instance.
One note: I didn't have a need to describe any multi-dimensional array types, so I didn't bother implementing description for that, but it would be fairly easy to add by calling type.GetArrayRank()
.
A minimal work solution that leverages CodeDomProvider
is to control how the CodeTypeReference
instance is built in the first place. There are special cases only for generic types and multi-rank arrays, so we only have to care about those:
static CodeTypeReference CreateTypeReference(Type type)
{
var typeName = (type.IsPrimitive || type == typeof(string)) ? type.FullName : type.Name;
var reference = new CodeTypeReference(typeName);
if (type.IsArray)
{
reference.ArrayElementType = CreateTypeReference(type.GetElementType());
reference.ArrayRank = type.GetArrayRank();
}
if (type.IsGenericType)
{
foreach (var argument in type.GetGenericArguments())
{
reference.TypeArguments.Add(CreateTypeReference(argument));
}
}
return reference;
}
Using this modified factory method, it is then possible to use the appropriate code provider to get pretty typing, like so:
using (var provider = new CSharpCodeProvider())
{
var reference = CreateTypeReference(typeof(IObservable<IEnumerable<Tuple<int?, string>>>[,]));
var output = provider.GetTypeOutput(reference);
Console.WriteLine(output);
}
The above code returns IObservable<IEnumerable<Tuple<Nullable<int>, string>>>[,]
. The only special case that is not handled well are Nullable
types but this is really more a fault of the CodeDomProvider
than anything else.
You have to write this yourself. Keep in mind that Type.Name
etc. are invoking methods that live in the CLR and can be invoked from multiple languages. This is why they don't come back looking like C# or VB or the language the caller was coded in, but instead looking like the CLR representation.
Note further that string
and what not are aliases for CLR types like System.String
. Again, this plays a role in the formatting that you see.
It's not hard to do using reflection, but I question the value of it.
First: Kudos to Navid for wheel reinvention avoidance. I would upvote if I could.
Here are a few points to add, if anyone goes down this path (at least for VS10/.Net 4):
* Try using one of the variants of CodeTypeReference with Type arguments. This avoids a number of pitfalls of using string type names (such as trailing &) and means you get back bool
instead of System.Boolean
etc. You do get back the full namespace for a lot of types like this but you can always get rid of the namespace part later.
* Simple Nullables tend to come back in the form System.Nullable<int>
rather than int?
- You can convert to the nicer syntax with a regex on the answer such as:
const string NullablePattern = @"System.Nullable<(?<nulledType>[\w\.]+)>";
const string NullableReplacement = @"${nulledType}?";
answer = Regex.Replace(answer, NullablePattern, NullableReplacement);
* The Type of a method argument like out T?
gives a very opaque string. If anyone has an elegant way of dealing with things like this I would love to know about it.
Hopefully this all becomes very easy with Roslyn.
as in your example you can expect the type so you can try that
public class test<T> where T : class
{
public List<String> tt
{
get;
set;
}
}
///////////////////////////
test<List<String>> tt = new test<List<String>>();
if(tt.GetType().FullName.Contains(TypeOf(List<String>).FullName))
{
//do something
}
else
{
//do something else
}
I understand that you want to compare types.
The best way to do this is...
myVar is List<string>
or
myVar.GetType() == myOtherVar.GetType()
If you don't need this... please disregard my answer.
I understood, that I have to write this myself. Here is my solution (it is actually a bit more than asked for). It is, probably, helpfull.
using System.Reflection;
using HWClassLibrary.Debug;
using System.Collections.Generic;
using System.Linq;
using System;
namespace HWClassLibrary.Helper
{
public static class TypeNameExtender
{
private static IEnumerable<Type> _referencedTypesCache;
public static void OnModuleLoaded() { _referencedTypesCache = null; }
public static string PrettyName(this Type type)
{
if(type == typeof(int))
return "int";
if(type == typeof(string))
return "string";
var result = PrettyTypeName(type);
if(type.IsGenericType)
result = result + PrettyNameForGeneric(type.GetGenericArguments());
return result;
}
private static string PrettyTypeName(Type type)
{
var result = type.Name;
if(type.IsGenericType)
result = result.Remove(result.IndexOf('`'));
if (type.IsNested && !type.IsGenericParameter)
return type.DeclaringType.PrettyName() + "." + result;
if(type.Namespace == null)
return result;
var conflictingTypes = ReferencedTypes
.Where(definedType => definedType.Name == type.Name && definedType.Namespace != type.Namespace)
.ToArray();
var namespaceParts = type.Namespace.Split('.').Reverse().ToArray();
var namespacePart = "";
for(var i = 0; i < namespaceParts.Length && conflictingTypes.Length > 0; i++)
{
namespacePart = namespaceParts[i] + "." + namespacePart;
conflictingTypes = conflictingTypes
.Where(conflictingType => (conflictingType.Namespace + ".").EndsWith(namespacePart))
.ToArray();
}
return namespacePart + result;
}
private static IEnumerable<Type> ReferencedTypes
{
get
{
if(_referencedTypesCache == null)
_referencedTypesCache = Assembly.GetEntryAssembly().GetReferencedTypes();
return _referencedTypesCache;
}
}
private static string PrettyNameForGeneric(Type[] types)
{
var result = "";
var delim = "<";
foreach(var t in types)
{
result += delim;
delim = ",";
result += t.PrettyName();
}
return result + ">";
}
}
}
I do it like this ..
public static class TypeExtensions {
public static String GetName(this Type t) {
String readable;
#if PREVENT_RECURSION
if(m_names.TryGetValue(t, out readable)) {
return readable;
}
#endif
var tArgs = t.IsGenericType ? t.GetGenericArguments() : Type.EmptyTypes;
var name = t.Name;
var format = Regex.Replace(name, @"`\d+.*", "")+(t.IsGenericType ? "<?>" : "");
var names = tArgs.Select(x => x.IsGenericParameter ? "" : GetName(x));
readable=String.Join(String.Join(",", names), format.Split('?'));
#if PREVENT_RECURSION
m_names.Add(t, readable);
#endif
return readable;
}
static readonly Dictionary<Type, String> m_names = new Dictionary<Type, String> { };
}
where PREVENT_RECURSION
should be defined true
if you don't want the type in generic type arguments be resolved recursively every time, just take from the cache dictionary; leave it undefined of false if you don't care that.
Combined a few answers, added support for nested types and array ranks, and turned it into an extension method with cached resolved names.
/// <summary>
/// Extension methods for <see cref="Type"/>.
/// </summary>
public static class TypeExtensions
{
/// <summary>
/// Dictionary of type names.
/// </summary>
private static readonly ConcurrentDictionary<Type, string> typeNames;
/// <summary>
/// Initializes static members of the <see cref="TypeExtensions"/> class.
/// </summary>
static TypeExtensions()
{
typeNames = new ConcurrentDictionary<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"
};
}
/// <summary>
/// Gets the type name with generics and array ranks resolved.
/// </summary>
/// <param name="type">
/// The type whose name to resolve.
/// </param>
/// <returns>
/// The resolved type name.
/// </returns>
public static string ToCSTypeName(this Type type)
{
return typeNames.GetOrAdd(type, GetPrettyTypeName);
}
/// <summary>
/// Gets the type name as it would be written in C#
/// </summary>
/// <param name="type">
/// The type whose name is to be written.
/// </param>
/// <returns>
/// The type name as it is written in C#
/// </returns>
private static string GetPrettyTypeName(Type type)
{
var typeNamespace = type.DeclaringType != null ? ToCSTypeName(type.DeclaringType) : type.Namespace;
if (type.IsGenericType)
{
if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return $"{ToCSTypeName(Nullable.GetUnderlyingType(type))}?";
}
var typeList = string.Join(", ", type.GenericTypeArguments.Select(ToCSTypeName).ToArray());
var typeName = type.Name.Split('`')[0];
return $"{typeNamespace}.{typeName}<{typeList}>";
}
if (type.IsArray)
{
var arrayRank = string.Empty.PadLeft(type.GetArrayRank() - 1, ',');
var elementType = ToCSTypeName(type.GetElementType());
return $"{elementType}[{arrayRank}]";
}
return $"{typeNamespace}.{type.Name}";
}
}
public static string pretty_name( Type type, int recursion_level = -1, bool expand_nullable = false )
{
if( type.IsArray )
{
return $"{pretty_name( type.GetElementType(), recursion_level, expand_nullable )}[]";
}
if( type.IsGenericType )
{
// find generic type name
var gen_type_name = type.GetGenericTypeDefinition().Name;
var index = gen_type_name.IndexOf( '`' );
if( index != -1 )
gen_type_name = gen_type_name.Substring( 0, index );
// retrieve generic type aguments
var arg_names = new List<string>();
var gen_type_args = type.GetGenericArguments();
foreach( var gen_type_arg in gen_type_args )
{
arg_names.Add(
recursion_level != 0
? pretty_name( gen_type_arg, recursion_level - 1, expand_nullable )
: "?" );
}
// if type is nullable and want compact notation '?'
if( !expand_nullable && Nullable.GetUnderlyingType( type ) != null )
return $"{arg_names[ 0 ]}?";
// compose common generic type format "T<T1, T2, ...>"
return $"{gen_type_name}<{string.Join( ", ", arg_names )}>";
}
return type.Name;
}
My two cents:
- Everything is done through the Type interface
- No Regex
- No extra objects created except for the list that holds generic argument names
- Supports infinite recursion or recursion to a certain level (or no recursion at all!)
- Supports nullable types (both formats "Nullable<>" and "?")
- Supports ranked arrays
© 2022 - 2024 — McMap. All rights reserved.