using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics.CodeAnalysis;
namespace Phantom.XRMOD.UnityFusion.Editor
{
///
/// Extension methods for .
///
public static class TypeExtensions
{
private static readonly Dictionary> toStringCache = new Dictionary>(3)
{
{ '\0', new Dictionary(4096) {
{ typeof(int), "Int" }, { typeof(uint), "UInt" },
{ typeof(float), "Float" },
{ typeof(double), "Double" },
{ typeof(bool), "Boolean" },
{ typeof(string), "String" },
{ typeof(short), "Short" }, { typeof(ushort), "UShort" },
{ typeof(byte), "Byte" },{ typeof(sbyte), "SByte" },
{ typeof(long), "Long" }, { typeof(ulong), "ULong" },
{ typeof(object), "object" },
{ typeof(decimal), "Decimal" }
} },
{ '/', new Dictionary(4096) {
{ typeof(int), "Int" }, { typeof(uint), "UInteger" },
{ typeof(float), "Float" },
{ typeof(double), "Double" },
{ typeof(bool), "Boolean" },
{ typeof(string), "String" },
{ typeof(short), "Short" }, { typeof(ushort), "UShort" },
{ typeof(byte), "Byte" },{ typeof(sbyte), "SByte" },
{ typeof(long), "Long" }, { typeof(ulong), "ULong" },
{ typeof(object), "System/Object" },
{ typeof(decimal), "Decimal" }
} },
{ '.', new Dictionary(4096) {
{ typeof(int), "Int" }, { typeof(uint), "UInt" },
{ typeof(float), "Float" },
{ typeof(double), "Double" },
{ typeof(bool), "Boolean" },
{ typeof(string), "String" },
{ typeof(short), "Short" }, { typeof(ushort), "UShort" },
{ typeof(byte), "Byte" },{ typeof(sbyte), "SByte" },
{ typeof(long), "Long" }, { typeof(ulong), "ULong" },
{ typeof(object), "System.Object" },
{ typeof(decimal), "Decimal" }
} }
};
[return: NotNull]
public static string ToHumanReadableString([AllowNull] this Type type, char namespaceDelimiter = '\0')
{
return type is null ? "Null" : ToString(type, namespaceDelimiter, toStringCache);
}
[return: NotNull]
internal static string ToString([DisallowNull] Type type, char namespaceDelimiter, Dictionary> cache)
{
if(cache[namespaceDelimiter].TryGetValue(type, out string cached))
{
return cached;
}
var builder = new StringBuilder();
ToString(type, builder, namespaceDelimiter, cache);
string result = builder.ToString();
cache[namespaceDelimiter][type] = result;
return result;
}
[return: NotNull]
private static void ToString([DisallowNull] Type type, [DisallowNull] StringBuilder builder, char namespaceDelimiter, Dictionary> cache, Type[] genericTypeArguments = null)
{
// E.g. List generic parameter is T, in which case we just want to append "T".
if(type.IsGenericParameter)
{
builder.Append(type.Name);
return;
}
if(type.IsArray)
{
builder.Append(ToString(type.GetElementType(), namespaceDelimiter, cache));
int rank = type.GetArrayRank();
switch(rank)
{
case 1:
builder.Append("[]");
break;
case 2:
builder.Append("[,]");
break;
case 3:
builder.Append("[,,]");
break;
default:
builder.Append('[');
for(int n = 1; n < rank; n++)
{
builder.Append(',');
}
builder.Append(']');
break;
}
return;
}
if(namespaceDelimiter != '\0' && type.Namespace != null)
{
var namespacePart = type.Namespace;
if(namespaceDelimiter != '.')
{
namespacePart = namespacePart.Replace('.', namespaceDelimiter);
}
builder.Append(namespacePart);
builder.Append(namespaceDelimiter);
}
#if CSHARP_7_3_OR_NEWER
// You can create instances of a constructed generic type.
// E.g. Dictionary instead of Dictionary.
if(type.IsConstructedGenericType)
{
genericTypeArguments = type.GenericTypeArguments;
}
#endif
// If this is a nested class type then append containing type(s) before continuing.
var containingClassType = type.DeclaringType;
if(containingClassType != null && type != containingClassType)
{
// GenericTypeArguments can't be fetched from the containing class type
// so need to pass them along to the ToString method and use them instead of
// the results of GetGenericArguments.
ToString(containingClassType, builder, '\0', cache, genericTypeArguments);
builder.Append('.');
}
if(!type.IsGenericType)
{
builder.Append(type.Name);
return;
}
var nullableUnderlyingType = Nullable.GetUnderlyingType(type);
if(nullableUnderlyingType != null)
{
// "Int?" instead of "Nullable"
builder.Append(ToString(nullableUnderlyingType, '\0', cache));
builder.Append("?");
return;
}
var name = type.Name;
// If type name doesn't end with `1, `2 etc. then it's not a generic class type
// but type of non-generic class nested inside a generic class.
if(name[^2] is '`')
{
// E.g. TKey, TValue
var genericTypes = type.GetGenericArguments();
builder.Append(name.Substring(0, name.Length - 2));
builder.Append('<');
// Prefer using results of GenericTypeArguments over results of GetGenericArguments if available.
int genericTypeArgumentsLength = genericTypeArguments != null ? genericTypeArguments.Length : 0;
if(genericTypeArgumentsLength > 0)
{
builder.Append(ToString(genericTypeArguments[0], '\0', cache));
}
else
{
builder.Append(ToString(genericTypes[0], '\0', cache));
}
for(int n = 1, count = genericTypes.Length; n < count; n++)
{
builder.Append(", ");
if(genericTypeArgumentsLength > n)
{
builder.Append(ToString(genericTypeArguments[n], '\0', cache));
}
else
{
builder.Append(ToString(genericTypes[n], '\0', cache));
}
}
builder.Append('>');
}
else
{
builder.Append(name);
}
}
}
}