#nullable enable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; namespace Barotrauma { public static class ReflectionUtils { private static readonly Dictionary> cachedNonAbstractTypes = new Dictionary>(); public static IEnumerable GetDerivedNonAbstract() { Assembly assembly = typeof(T).Assembly; if (!cachedNonAbstractTypes.ContainsKey(assembly)) { cachedNonAbstractTypes[assembly] = assembly.GetTypes() .Where(t => !t.IsAbstract).ToImmutableArray(); } return cachedNonAbstractTypes[assembly].Where(t => t.IsSubclassOf(typeof(T))); } public static Option ParseDerived(TInput input) where TInput : notnull { static Option none() => Option.None(); var derivedTypes = GetDerivedNonAbstract(); Option parseOfType(Type t) { //every TBase type is expected to have a method with the following signature: // public static Option Parse(TInput str) var parseFunc = t.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static); if (parseFunc is null) { return none(); } var parameters = parseFunc.GetParameters(); if (parameters.Length != 1) { return none(); } var returnType = parseFunc.ReturnType; if (!returnType.IsConstructedGenericType) { return none(); } if (returnType.GetGenericTypeDefinition() != typeof(Option<>)) { return none(); } if (returnType.GenericTypeArguments[0] != t) { return none(); } //some hacky business to convert from Option to Option when we only know T2 at runtime static Option convert(Option option) where T2 : TBase => option.Select(v => (TBase)v); Func, Option> f = convert; var genericArgs = f.Method.GetGenericArguments(); genericArgs[^1] = t; var constructedConverter = f.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArgs); return constructedConverter.Invoke(null, new[] { parseFunc.Invoke(null, new object[] { input }) }) as Option ?? none(); } return derivedTypes.Select(parseOfType).FirstOrDefault(t => t.IsSome()) ?? none(); } } }