// Reduce IL2CPP code generate size(don't write long code in <T>)
        internal static object GetFormatter(Type t)
        {
            if (t.IsArray)
            {
                var rank = t.GetArrayRank();
                if (rank == 1)
                {
                    if (t.GetElementType() == typeof(byte))                     // byte[] is also supported in builtin formatter.
                    {
                        return(ByteArrayFormatter.Default);
                    }

                    return(Activator.CreateInstance(typeof(ArrayFormatter <>).MakeGenericType(t.GetElementType())));
                }
                if (rank == 2)
                {
                    return(Activator.CreateInstance(typeof(TwoDimensionalArrayFormatter <>).MakeGenericType(t.GetElementType())));
                }

                if (rank == 3)
                {
                    return(Activator.CreateInstance(typeof(ThreeDimensionalArrayFormatter <>).MakeGenericType(t.GetElementType())));
                }

                if (rank == 4)
                {
                    return(Activator.CreateInstance(typeof(FourDimensionalArrayFormatter <>).MakeGenericType(t.GetElementType())));
                }

                return(null);                // not supported built-in
            }

            if (t.IsGenericType)
            {
                var genericType         = t.GetGenericTypeDefinition();
                var isNullable          = ReflectionExtensions.IsNullable(genericType);
                var nullableElementType = isNullable ? t.GenericTypeArguments[0] : null;

                if (genericType == typeof(KeyValuePair <,>))
                {
                    return(CreateInstance(typeof(KeyValuePairFormatter <,>), t.GenericTypeArguments));
                }

                if (isNullable && nullableElementType.IsConstructedGenericType &&
                    nullableElementType.GetGenericTypeDefinition() == typeof(KeyValuePair <,>))
                {
                    return(CreateInstance(typeof(NullableFormatter <>), new[] { nullableElementType }));
                }
#if NETSTANDARD2_1
                // ValueTask
                if (genericType == typeof(ValueTask <>))
                {
                    return(CreateInstance(typeof(ValueTaskFormatter <>), t.GenericTypeArguments));
                }

                if (isNullable && nullableElementType.IsConstructedGenericType && nullableElementType.GetGenericTypeDefinition() == typeof(ValueTask <>))
                {
                    return(CreateInstance(typeof(NullableFormatter <>), new[] { nullableElementType }));
                }
#endif

                // Tuple
                if (t.FullName.StartsWith("System.Tuple"))
                {
                    Type tupleFormatterType = null;
                    switch (t.GenericTypeArguments.Length)
                    {
                    case 1:
                        tupleFormatterType = typeof(TupleFormatter <>);
                        break;

                    case 2:
                        tupleFormatterType = typeof(TupleFormatter <,>);
                        break;

                    case 3:
                        tupleFormatterType = typeof(TupleFormatter <, ,>);
                        break;

                    case 4:
                        tupleFormatterType = typeof(TupleFormatter <, , ,>);
                        break;

                    case 5:
                        tupleFormatterType = typeof(TupleFormatter <, , , ,>);
                        break;

                    case 6:
                        tupleFormatterType = typeof(TupleFormatter <, , , , ,>);
                        break;

                    case 7:
                        tupleFormatterType = typeof(TupleFormatter <, , , , , ,>);
                        break;

                    case 8:
                        tupleFormatterType = typeof(TupleFormatter <, , , , , , ,>);
                        break;

                    default:
                        break;
                    }

                    return(CreateInstance(tupleFormatterType, t.GenericTypeArguments));
                }

#if NETSTANDARD
                // ValueTuple
                if (t.FullName.StartsWith("System.ValueTuple"))
                {
                    Type tupleFormatterType = null;
                    switch (t.GenericTypeArguments.Length)
                    {
                    case 1:
                        tupleFormatterType = typeof(ValueTupleFormatter <>);
                        break;

                    case 2:
                        tupleFormatterType = typeof(ValueTupleFormatter <,>);
                        break;

                    case 3:
                        tupleFormatterType = typeof(ValueTupleFormatter <, ,>);
                        break;

                    case 4:
                        tupleFormatterType = typeof(ValueTupleFormatter <, , ,>);
                        break;

                    case 5:
                        tupleFormatterType = typeof(ValueTupleFormatter <, , , ,>);
                        break;

                    case 6:
                        tupleFormatterType = typeof(ValueTupleFormatter <, , , , ,>);
                        break;

                    case 7:
                        tupleFormatterType = typeof(ValueTupleFormatter <, , , , , ,>);
                        break;

                    case 8:
                        tupleFormatterType = typeof(ValueTupleFormatter <, , , , , , ,>);
                        break;

                    default:
                        break;
                    }

                    return(CreateInstance(tupleFormatterType, t.GenericTypeArguments));
                }

                // Nullable ValueTuple
                if (isNullable && nullableElementType.IsConstructedGenericType &&
                    nullableElementType.FullName.StartsWith("System.ValueTuple"))
                {
                    Type tupleFormatterType = null;
                    switch (nullableElementType.GenericTypeArguments.Length)
                    {
                    case 1:
                        tupleFormatterType = typeof(ValueTupleFormatter <>);
                        break;

                    case 2:
                        tupleFormatterType = typeof(ValueTupleFormatter <,>);
                        break;

                    case 3:
                        tupleFormatterType = typeof(ValueTupleFormatter <, ,>);
                        break;

                    case 4:
                        tupleFormatterType = typeof(ValueTupleFormatter <, , ,>);
                        break;

                    case 5:
                        tupleFormatterType = typeof(ValueTupleFormatter <, , , ,>);
                        break;

                    case 6:
                        tupleFormatterType = typeof(ValueTupleFormatter <, , , , ,>);
                        break;

                    case 7:
                        tupleFormatterType = typeof(ValueTupleFormatter <, , , , , ,>);
                        break;

                    case 8:
                        tupleFormatterType = typeof(ValueTupleFormatter <, , , , , , ,>);
                        break;

                    default:
                        break;
                    }

                    var tupleFormatter = CreateInstance(tupleFormatterType, nullableElementType.GenericTypeArguments);
                    return(CreateInstance(typeof(StaticNullableFormatter <>), new[] { nullableElementType }, tupleFormatter));
                }
#endif

                // ArraySegment
                if (genericType == typeof(ArraySegment <>))
                {
                    if (t.GenericTypeArguments[0] == typeof(byte))
                    {
                        return(ByteArraySegmentFormatter.Default);
                    }

                    return(CreateInstance(typeof(ArraySegmentFormatter <>), t.GenericTypeArguments));
                }

                if (isNullable && nullableElementType.IsConstructedGenericType &&
                    nullableElementType.GetGenericTypeDefinition() == typeof(ArraySegment <>))
                {
                    if (nullableElementType == typeof(ArraySegment <byte>))
                    {
                        return(new StaticNullableFormatter <ArraySegment <byte> >(ByteArraySegmentFormatter.Default));
                    }

                    return(CreateInstance(typeof(NullableFormatter <>), new[] { nullableElementType }));
                }

                // Mapped formatter
                if (FormatterMap.TryGetValue(genericType, out var formatterType))
                {
                    return(CreateInstance(formatterType, t.GenericTypeArguments));
                }

                // generic collection
                if (t.GenericTypeArguments.Length == 1 &&
                    t.GetInterfaces().Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ICollection <>)) &&
                    t.GetDeclaredConstructors().Any(x => x.GetParameters().Length == 0))
                {
                    var elemType = t.GenericTypeArguments[0];
                    return(CreateInstance(typeof(GenericCollectionFormatter <,>), new[] { elemType, t }));
                }
                // generic dictionary
                if (t.GenericTypeArguments.Length == 2 &&
                    t.GetInterfaces().Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary <,>)) &&
                    t.GetDeclaredConstructors().Any(x => x.GetParameters().Length == 0))
                {
                    var keyType   = t.GenericTypeArguments[0];
                    var valueType = t.GenericTypeArguments[1];
                    return(CreateInstance(typeof(GenericDictionaryFormatter <, ,>), new[] { keyType, valueType, t }));
                }
            }
            else
            {
                // NonGeneric Collection
                if (t == typeof(IEnumerable))
                {
                    return(NonGenericInterfaceEnumerableFormatter.Default);
                }

                if (t == typeof(ICollection))
                {
                    return(NonGenericInterfaceCollectionFormatter.Default);
                }

                if (t == typeof(IList))
                {
                    return(NonGenericInterfaceListFormatter.Default);
                }

                if (t == typeof(IDictionary))
                {
                    return(NonGenericInterfaceDictionaryFormatter.Default);
                }

                if (typeof(IList).IsAssignableFrom(t) && t.GetDeclaredConstructors().Any(x => x.GetParameters().Length == 0))
                {
                    return(Activator.CreateInstance(typeof(NonGenericListFormatter <>).MakeGenericType(t)));
                }

                if (typeof(IDictionary).IsAssignableFrom(t) && t.GetDeclaredConstructors().Any(x => x.GetParameters().Length == 0))
                {
                    return(Activator.CreateInstance(typeof(NonGenericDictionaryFormatter <>).MakeGenericType(t)));
                }
            }

            return(null);
        }