///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // S H A R P L I B // /// // (c) 2003..2025 using System; using System.IO; using System.Xml; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Linq; using System.Collections.Immutable; using System.Collections.Concurrent; using System.Collections.Immutable; namespace ser; public record CodeGenConfig : io.Recorded { // Whitelists (if needed, otherwise rely on attributes/defaults) public ImmutableDictionary> WLProps { get; init; } = ImmutableDictionary>.Empty; public ImmutableDictionary> WLFields { get; init; } = ImmutableDictionary>.Empty; // Default member types to process public ser.Types TypesDefault { get; init; } = ser.Types.Fields | ser.Types.Props; // How to handle backing fields (might be less relevant for code gen) public BackingFieldNaming Naming { get; init; } = BackingFieldNaming.Regular; public static CodeGenConfig Default { get; } = new CodeGenConfig(); } public record GenMemberMeta( MemberInfo Info, Type Type, string Name, // Name for code generation (usually original) bool IsPrimitive, bool IsCollection, Type? CollectionElementType, bool HasDo, bool HasDont ); public record TypeStructureInfo( Type Type, List Members, bool IsValueType, bool IsCollection ); public class TypeStructureAnalyzer { private readonly ConcurrentDictionary _cache = new(); private readonly CodeGenConfig _cfg; public TypeStructureAnalyzer( CodeGenConfig cfg ) => _cfg = cfg; public TypeStructureInfo Get( Type type ) => _cache.GetOrAdd( type, BuildTypeInfo ); private TypeStructureInfo BuildTypeInfo( Type type ) { var members = new List(); var typesTodo = type.GetCustomAttribute( true )?.Types ?? _cfg.TypesDefault; bool doFields = typesTodo.HasFlag( ser.Types.Fields ); bool doProps = typesTodo.HasFlag( ser.Types.Props ); // Track processed names to avoid duplicates (e.g., field + prop) var processedNames = new HashSet(); // Process Properties First (often preferred interface) if( doProps ) { foreach( var pi in refl.GetAllProperties( type ) ) { if( ProcessMember( pi, false, false, new HashSet(), false, members ) ) { processedNames.Add( pi.Name ); } } } // Process Fields, avoiding those already covered by properties if( doFields ) { foreach( var fi in refl.GetAllFields( type ) ) { var (isBacking, propName) = IsBackingField( fi ); string nameToTest = isBacking ? propName : fi.Name; if( !processedNames.Contains( nameToTest ) ) { if( ProcessMember( fi, false, false, new HashSet(), false, members ) ) { processedNames.Add( nameToTest ); } } } } return new TypeStructureInfo( type, members, type.IsValueType, typeof( IEnumerable ).IsAssignableFrom( type ) && type != typeof( string ) ); } private bool ProcessMember( MemberInfo mi, bool filter, bool doImpls, HashSet whitelist, bool isImm, List members ) { var (hasDo, hasDont, propName) = GetMemberAttributes( mi, out var actualMiForAtts ); if( hasDont ) return false; if( mi.GetCustomAttribute( true ) != null ) return false; if( mi.Name.Contains( "k__BackingField" ) && !propName.Any() ) return false; // Skip if backing but no prop found string name = string.IsNullOrEmpty( propName ) ? mi.Name : propName; // Add filtering logic if needed (based on whitelist, etc.) var type = ( mi is FieldInfo fi ) ? fi.FieldType : ( (PropertyInfo)mi ).PropertyType; bool isCollection = typeof( IEnumerable ).IsAssignableFrom( type ) && type != typeof( string ); Type? elementType = isCollection ? GetElementType( type ) : null; bool isPrimitive = Type.GetTypeCode( type ) != TypeCode.Object && !isCollection; members.Add( new GenMemberMeta( mi, type, name, isPrimitive, isCollection, elementType, hasDo, hasDont ) ); return true; } private (bool, string) IsBackingField( FieldInfo fi ) { if( fi.Name.StartsWith( "<" ) && fi.Name.EndsWith( "BackingField" ) ) { var gtIndex = fi.Name.IndexOf( '>' ); if( gtIndex > 1 ) { return (true, fi.Name.Substring( 1, gtIndex - 1 )); } } return (false, ""); } private (bool hasDo, bool hasDont, string propName) GetMemberAttributes( MemberInfo mi, out MemberInfo actualMi ) { actualMi = mi; string propName = ""; if( mi is FieldInfo fi && IsBackingField( fi ).Item1 ) { propName = IsBackingField( fi ).Item2; var propInfo = mi.DeclaringType?.GetProperty( propName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ); if( propInfo != null ) actualMi = propInfo; } else if( mi is PropertyInfo ) { propName = mi.Name; } return ( actualMi.GetCustomAttribute() != null, actualMi.GetCustomAttribute() != null, propName ); } private Type GetElementType( Type collectionType ) { if( collectionType.IsArray ) return collectionType.GetElementType()!; if( collectionType.IsGenericType ) return collectionType.GetGenericArguments().Last(); // Usually last (e.g., List, Dict) return typeof( object ); // Fallback } // Add GetFilters and FilterField if needed, or simplify as above }