using System.ComponentModel.DataAnnotations; using System.Reflection; using Facepunch.ActionGraphs; namespace Sandbox; public static partial class SandboxSystemExtensions { /// /// Returns true if this member has this attribute /// internal static bool HasAttribute( this MemberInfo memberinfo, Type attribute, bool inherit = true ) { return memberinfo?.IsDefined( attribute, inherit ) ?? false; } /// /// Returns true if this type derives from a type named name /// internal static bool HasBaseType( this Type type, string name ) { if ( type.BaseType == null ) return false; if ( type.BaseType.FullName == name ) return true; return HasBaseType( type.BaseType, name ); } /// /// Gets an attribute on an enum field value /// /// The type of the attribute you want to retrieve /// The enum value /// The attribute of type T that exists on the enum value /// ().Description;]]> public static T GetAttributeOfType( this Enum enumVal ) where T : System.Attribute { var type = enumVal.GetType(); var memInfo = type.GetMember( enumVal.ToString() ); var attributes = memInfo[0].GetCustomAttributes( typeof( T ), false ); return (attributes.Length > 0) ? (T)attributes[0] : null; } /// /// Returns if this type is based on a given generic type. /// /// The type to test. /// The type to test against. Typically this will be something like typeof( MyType<> ) public static bool IsBasedOnGenericType( this Type src, Type test ) { if ( !test.IsGenericType ) return false; var type = src; while ( type != null ) { if ( type.IsGenericType && type.GetGenericTypeDefinition() == test ) return true; type = type.BaseType; } return false; } /// /// Check all s on this property, and get the error messages if there are any. /// /// The property whose arguments to test. /// Instance of the object this property is of. /// If returned false, these will be the error messages to display. /// Override the property name in error messages. /// Returns true if all checks have passed or there is no attributes to test, false if there were errors. public static bool CheckValidationAttributes( this PropertyInfo prop, object obj, out string[] errors, string name = null ) { if ( prop == null ) throw new System.ArgumentNullException(); if ( obj == null ) throw new System.ArgumentNullException(); var errorList = new List(); var attrs = prop.GetCustomAttributes(); foreach ( var attr in attrs ) { var valid = attr.IsValid( prop.GetValue( obj ) ); if ( !valid ) errorList.Add( attr.FormatErrorMessage( name ?? prop.Name ) ); } errors = errorList.ToArray(); return !errorList.Any(); } /// /// Determine if this property is init-only. /// /// The property to test. /// Returns true if the property is init-only, false otherwise. public static bool IsInitOnly( this PropertyInfo property ) { if ( !property.CanWrite ) return false; var setMethod = property.SetMethod; if ( setMethod == null ) return false; // Init-only properties are marked with the IsExternalInit type. return setMethod.ReturnParameter.GetRequiredCustomModifiers().Contains( typeof( System.Runtime.CompilerServices.IsExternalInit ) ); } /// /// Returns this type's name, with nicer formatting for generic types. /// public static string ToSimpleString( this Type type, bool includeNamespace = true ) { var sb = new StringBuilder(); sb.AppendType( type, includeNamespace ); return sb.ToString(); } /// /// Returns this member's name qualified by its declaring type, with nicer formatting for generics. /// public static string ToSimpleString( this MemberInfo member, bool includeNamespace = true ) { if ( member is Type type ) return ToSimpleString( type, includeNamespace ); if ( member is not MethodInfo { IsGenericMethod: true } method ) { return $"{member.DeclaringType}::{member.Name}"; } var sb = new StringBuilder(); sb.AppendType( method.DeclaringType, includeNamespace ); sb.Append( "::" ); var quoteIndex = method.Name.IndexOf( '`' ); sb.Append( quoteIndex == -1 ? method.Name : method.Name[..quoteIndex] ); sb.Append( "<" ); var first = true; foreach ( var arg in method.GetGenericArguments() ) { if ( first ) first = false; else sb.Append( ", " ); sb.AppendType( arg, includeNamespace ); } sb.Append( ">" ); return sb.ToString(); } /// /// Returns a nice name for the given delegate, based on the method that implements it. /// public static string ToSimpleString( this Delegate deleg, bool includeNamespace = true ) { if ( deleg.Method is { Name: "MoveNext", DeclaringType.Name: "AsyncStateMachineBox`1" } ) { var stateMachineType = deleg.Method.DeclaringType.GetGenericArguments()[1]; return stateMachineType.ToSimpleString(); } return deleg.Method.ToSimpleString(); } private static void AppendType( this StringBuilder sb, Type type, bool includeNamespace ) { if ( type == null ) { sb.Append( "null" ); return; } if ( type.IsGenericMethodParameter || type.IsGenericTypeParameter ) { return; } if ( type.IsArray ) { sb.AppendType( type.GetElementType(), includeNamespace ); sb.Append( "[" ); for ( var i = 1; i < type.GetArrayRank(); ++i ) { sb.Append( "," ); } sb.Append( "]" ); return; } if ( type.IsByRef ) { sb.Append( "ref " ); sb.AppendType( type.GetElementType(), includeNamespace ); return; } if ( type.IsPointer ) { sb.AppendType( type.GetElementType(), includeNamespace ); sb.Append( "*" ); return; } if ( type.IsNested ) { sb.AppendType( type.DeclaringType, includeNamespace ); sb.Append( "." ); } else if ( type.Namespace != null && includeNamespace ) { sb.Append( type.Namespace ); sb.Append( "." ); } if ( type.IsGenericType ) { if ( Either.IsEitherType( type ) ) { var options = Either.Unwrap( type ); var first = true; foreach ( var option in options ) { if ( first ) first = false; else sb.Append( " | " ); sb.AppendType( option, includeNamespace ); } } else if ( Nullable.GetUnderlyingType( type ) is { } elemType ) { sb.AppendType( elemType, includeNamespace ); return; } else { var quoteIndex = type.Name.IndexOf( '`' ); sb.Append( quoteIndex == -1 ? type.Name : type.Name[..quoteIndex] ); sb.Append( "<" ); var first = true; foreach ( var arg in type.GetGenericArguments() ) { if ( first ) first = false; else sb.Append( ", " ); sb.AppendType( arg, includeNamespace ); } sb.Append( ">" ); } return; } sb.Append( type.Name ); } }