using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.ExceptionServices; using System.Text; using System.Text.RegularExpressions; using JetBrains.Annotations; namespace Kyoo { /// /// A set of utility functions that can be used everywhere. /// public static class Utility { /// /// Is the lambda expression a member (like x => x.Body). /// /// The expression that should be checked /// True if the expression is a member, false otherwise public static bool IsPropertyExpression(LambdaExpression ex) { if (ex == null) return false; return ex.Body is MemberExpression || ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression; } /// /// Get the name of a property. Useful for selectors as members ex: Load(x => x.Shows) /// /// The expression /// The name of the expression /// If the expression is not a property, ArgumentException is thrown. public static string GetPropertyName(LambdaExpression ex) { if (!IsPropertyExpression(ex)) throw new ArgumentException($"{ex} is not a property expression."); MemberExpression member = ex.Body.NodeType == ExpressionType.Convert ? ((UnaryExpression)ex.Body).Operand as MemberExpression : ex.Body as MemberExpression; return member!.Member.Name; } /// /// Get the value of a member (property or field) /// /// The member value /// The owner of this member /// The value boxed as an object /// if or is null. /// The member is not a field or a property. public static object GetValue([NotNull] this MemberInfo member, [NotNull] object obj) { if (member == null) throw new ArgumentNullException(nameof(member)); if (obj == null) throw new ArgumentNullException(nameof(obj)); return member switch { PropertyInfo property => property.GetValue(obj), FieldInfo field => field.GetValue(obj), _ => throw new ArgumentException($"Can't get value of a non property/field (member: {member}).") }; } /// /// Slugify a string (Replace spaces by -, Uniformize accents é -> e) /// /// The string to slugify /// The slug version of the given string public static string ToSlug([CanBeNull] string str) { if (str == null) return null; str = str.ToLowerInvariant(); string normalizedString = str.Normalize(NormalizationForm.FormD); StringBuilder stringBuilder = new(); foreach (char c in normalizedString) { UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c); if (unicodeCategory != UnicodeCategory.NonSpacingMark) stringBuilder.Append(c); } str = stringBuilder.ToString().Normalize(NormalizationForm.FormC); str = Regex.Replace(str, @"\s", "-", RegexOptions.Compiled); str = Regex.Replace(str, @"[^\w\s\p{Pd}]", "", RegexOptions.Compiled); str = str.Trim('-', '_'); str = Regex.Replace(str, @"([-_]){2,}", "$1", RegexOptions.Compiled); return str; } /// /// Get the default value of a type. /// /// The type to get the default value /// The default value of the given type. public static object GetClrDefault(this Type type) { return type.IsValueType ? Activator.CreateInstance(type) : null; } /// /// Return every in the inheritance tree of the parameter (interfaces are not returned) /// /// The starting type /// A list of types /// can't be null public static IEnumerable GetInheritanceTree([NotNull] this Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); for (; type != null; type = type.BaseType) yield return type; } /// /// Check if inherit from a generic type . /// /// Does this object's type is a /// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>). /// True if obj inherit from genericType. False otherwise /// obj and genericType can't be null public static bool IsOfGenericType([NotNull] object obj, [NotNull] Type genericType) { if (obj == null) throw new ArgumentNullException(nameof(obj)); return IsOfGenericType(obj.GetType(), genericType); } /// /// Check if inherit from a generic type . /// /// The type to check /// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>). /// True if obj inherit from genericType. False otherwise /// obj and genericType can't be null public static bool IsOfGenericType([NotNull] Type type, [NotNull] Type genericType) { if (type == null) throw new ArgumentNullException(nameof(type)); if (genericType == null) throw new ArgumentNullException(nameof(genericType)); if (!genericType.IsGenericType) throw new ArgumentException($"{nameof(genericType)} is not a generic type."); IEnumerable types = genericType.IsInterface ? type.GetInterfaces() : type.GetInheritanceTree(); return types.Prepend(type) .Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType); } /// /// Get the generic definition of . /// For example, calling this function with List<string> and typeof(IEnumerable<>) will return IEnumerable<string> /// /// The type to check /// The generic type to check against (Only generic types are supported like typeof(IEnumerable<>). /// The generic definition of genericType that type inherit or null if type does not implement the generic type. /// and can't be null /// must be a generic type public static Type GetGenericDefinition([NotNull] Type type, [NotNull] Type genericType) { if (type == null) throw new ArgumentNullException(nameof(type)); if (genericType == null) throw new ArgumentNullException(nameof(genericType)); if (!genericType.IsGenericType) throw new ArgumentException($"{nameof(genericType)} is not a generic type."); IEnumerable types = genericType.IsInterface ? type.GetInterfaces() : type.GetInheritanceTree(); return types.Prepend(type) .FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType); } /// /// Retrieve a method from an with the given name and respect the /// amount of parameters and generic parameters. This works for polymorphic methods. /// /// /// The type owning the method. For non static methods, this is the this. /// /// /// The binding flags of the method. This allow you to specify public/private and so on. /// /// /// The name of the method. /// /// /// The list of generic parameters. /// /// /// The list of parameters. /// /// No method match the given constraints. /// The method handle of the matching method. [PublicAPI] [NotNull] public static MethodInfo GetMethod([NotNull] Type type, BindingFlags flag, string name, [NotNull] Type[] generics, [NotNull] object[] args) { if (type == null) throw new ArgumentNullException(nameof(type)); if (generics == null) throw new ArgumentNullException(nameof(generics)); if (args == null) throw new ArgumentNullException(nameof(args)); MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public) .Where(x => x.Name == name) .Where(x => x.GetGenericArguments().Length == generics.Length) .Where(x => x.GetParameters().Length == args.Length) .IfEmpty(() => throw new ArgumentException($"A method named {name} with " + $"{args.Length} arguments and {generics.Length} generic " + $"types could not be found on {type.Name}.")) // TODO this won't work but I don't know why. // .Where(x => // { // int i = 0; // return x.GetGenericArguments().All(y => y.IsAssignableFrom(generics[i++])); // }) // .IfEmpty(() => throw new NullReferenceException($"No method {name} match the generics specified.")) // TODO this won't work for Type because T is specified in arguments but not in the parameters type. // .Where(x => // { // int i = 0; // return x.GetParameters().All(y => y.ParameterType.IsInstanceOfType(args[i++])); // }) // .IfEmpty(() => throw new NullReferenceException($"No method {name} match the parameters's types.")) .Take(2) .ToArray(); if (methods.Length == 1) return methods[0]; throw new ArgumentException($"Multiple methods named {name} match the generics and parameters constraints."); } /// /// Run a generic static method for a runtime . /// /// /// To run for a List where you don't know the type at compile type, /// you could do: /// /// Utility.RunGenericMethod<object>( /// typeof(Utility), /// nameof(MergeLists), /// enumerableType, /// oldValue, newValue, equalityComparer) /// /// /// The type that owns the method. For non static methods, the type of this. /// The name of the method. You should use the nameof keyword. /// The generic type to run the method with. /// The list of arguments of the method /// /// The return type of the method. You can put for an unknown one. /// /// No method match the given constraints. /// The return of the method you wanted to run. /// /// public static T RunGenericMethod( [NotNull] Type owner, [NotNull] string methodName, [NotNull] Type type, params object[] args) { return RunGenericMethod(owner, methodName, new[] {type}, args); } /// /// Run a generic static method for a multiple runtime . /// If your generic method only needs one type, see /// /// /// /// To run for a List where you don't know the type at compile type, /// you could do: /// /// Utility.RunGenericMethod<object>( /// typeof(Utility), /// nameof(MergeLists), /// enumerableType, /// oldValue, newValue, equalityComparer) /// /// /// The type that owns the method. For non static methods, the type of this. /// The name of the method. You should use the nameof keyword. /// The list of generic types to run the method with. /// The list of arguments of the method /// /// The return type of the method. You can put for an unknown one. /// /// No method match the given constraints. /// The return of the method you wanted to run. /// /// [PublicAPI] public static T RunGenericMethod( [NotNull] Type owner, [NotNull] string methodName, [NotNull] Type[] types, params object[] args) { if (owner == null) throw new ArgumentNullException(nameof(owner)); if (methodName == null) throw new ArgumentNullException(nameof(methodName)); if (types == null) throw new ArgumentNullException(nameof(types)); if (types.Length < 1) throw new ArgumentException($"The {nameof(types)} array is empty. At least one type is needed."); MethodInfo method = GetMethod(owner, BindingFlags.Static, methodName, types, args); return (T)method.MakeGenericMethod(types).Invoke(null, args); } /// /// Run a generic method for a runtime . /// /// /// To run for a List where you don't know the type at compile type, /// you could do: /// /// Utility.RunGenericMethod<object>( /// typeof(Utility), /// nameof(MergeLists), /// enumerableType, /// oldValue, newValue, equalityComparer) /// /// /// The this of the method to run. /// The name of the method. You should use the nameof keyword. /// The generic type to run the method with. /// The list of arguments of the method /// /// The return type of the method. You can put for an unknown one. /// /// No method match the given constraints. /// The return of the method you wanted to run. /// /// public static T RunGenericMethod( [NotNull] object instance, [NotNull] string methodName, [NotNull] Type type, params object[] args) { return RunGenericMethod(instance, methodName, new[] {type}, args); } /// /// Run a generic method for a multiple runtime . /// If your generic method only needs one type, see /// /// /// /// To run for a List where you don't know the type at compile type, /// you could do: /// /// Utility.RunGenericMethod<object>( /// typeof(Utility), /// nameof(MergeLists), /// enumerableType, /// oldValue, newValue, equalityComparer) /// /// /// The this of the method to run. /// The name of the method. You should use the nameof keyword. /// The list of generic types to run the method with. /// The list of arguments of the method /// /// The return type of the method. You can put for an unknown one. /// /// No method match the given constraints. /// The return of the method you wanted to run. /// /// public static T RunGenericMethod( [NotNull] object instance, [NotNull] string methodName, [NotNull] Type[] types, params object[] args) { if (instance == null) throw new ArgumentNullException(nameof(instance)); if (methodName == null) throw new ArgumentNullException(nameof(methodName)); if (types == null || types.Length == 0) throw new ArgumentNullException(nameof(types)); MethodInfo method = GetMethod(instance.GetType(), BindingFlags.Instance, methodName, types, args); return (T)method.MakeGenericMethod(types).Invoke(instance, args.ToArray()); } public static string ToQueryString(this Dictionary query) { if (!query.Any()) return string.Empty; return "?" + string.Join('&', query.Select(x => $"{x.Key}={x.Value}")); } [System.Diagnostics.CodeAnalysis.DoesNotReturn] public static void ReThrow([NotNull] this Exception ex) { if (ex == null) throw new ArgumentNullException(nameof(ex)); ExceptionDispatchInfo.Capture(ex).Throw(); } /// /// Get a friendly type name (supporting generics) /// For example a list of string will be displayed as List<string> and not as List`1. /// /// The type to use /// The friendly name of the type public static string FriendlyName(this Type type) { if (!type.IsGenericType) return type.Name; string generics = string.Join(", ", type.GetGenericArguments().Select(x => x.FriendlyName())); return $"{type.Name[..type.Name.IndexOf('`')]}<{generics}>"; } } }