#nullable enable namespace UnitTests_Parallelizable.ConfigurationTests; using System; using System.Collections; using System.Collections.Concurrent; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; public static class MemorySizeEstimator { public static long EstimateSize (T? source) { if (source is null) { return 0; } ConcurrentDictionary visited = new (ReferenceEqualityComparer.Instance); return EstimateSizeInternal (source, visited); } private const int POINTER_SIZE = 8; // 64-bit system private const int OBJECT_HEADER_SIZE = 16; // 2 pointers for GC private static long EstimateSizeInternal (object? source, ConcurrentDictionary visited) { if (source is null) { return 0; } // Handle already visited objects to avoid circular references if (visited.TryGetValue (source, out long existingSize)) { // // Log revisited object (enable for debugging) // Console.WriteLine($"Revisited {source.GetType().FullName}: {existingSize} bytes"); return existingSize; } Type type = source.GetType (); long size = 0; // Handle simple types if (IsSimpleType (type)) { size = EstimateSimpleTypeSize (source, type); visited.TryAdd (source, size); // // Log simple type (enable for debugging) // Console.WriteLine($"{type.FullName}: {size} bytes"); return size; } // Handle arrays if (type.IsArray) { size = EstimateArraySize (source, visited); } // Handle dictionaries else if (source is IDictionary) { size = EstimateDictionarySize (source, visited); } // Handle collections else if (typeof (ICollection).IsAssignableFrom (type)) { size = EstimateCollectionSize (source, visited); } // Handle structs and classes else { size = EstimateObjectSize (source, type, visited); } visited.TryAdd (source, size); // // Log object size (enable for debugging) // if (size == 0) // { // Console.WriteLine($"Zero size for {type.FullName}"); // } // else // { // Console.WriteLine($"{type.FullName}: {size} bytes"); // } return size; } private static bool IsSimpleType (Type type) { if (type.IsPrimitive || type.IsEnum || type == typeof (decimal) || type == typeof (DateTime) || type == typeof (DateTimeOffset) || type == typeof (TimeSpan) || type == typeof (Guid) || type == typeof (Rune) || type == typeof (string)) { return true; } // Treat structs with no writable public properties as simple types if (type.IsValueType) { PropertyInfo [] writableProperties = type.GetProperties (BindingFlags.Instance | BindingFlags.Public) .Where (p => p is { CanRead: true, CanWrite: true } && p.GetIndexParameters ().Length == 0) .ToArray (); return writableProperties.Length == 0; } // Treat Property翰Info as simple (metadata, not cloned) if (typeof (PropertyInfo).IsAssignableFrom (type)) { return true; } return false; } private static long EstimateSimpleTypeSize (object source, Type type) { if (type == typeof (string)) { string str = (string)source; // Header + length (4) + char array ref + chars (2 bytes each) return OBJECT_HEADER_SIZE + 4 + POINTER_SIZE + (str.Length * 2); } try { return Marshal.SizeOf (type); } catch (ArgumentException) { // Fallback for enums or other simple types return 4; // Conservative estimate } } private static long EstimateArraySize (object source, ConcurrentDictionary visited) { Array array = (Array)source; long size = OBJECT_HEADER_SIZE + 4 + POINTER_SIZE; // Header + length + padding foreach (object? element in array) { size += EstimateSizeInternal (element, visited); } return size; } private static long EstimateDictionarySize (object source, ConcurrentDictionary visited) { IDictionary dict = (IDictionary)source; long size = OBJECT_HEADER_SIZE + (POINTER_SIZE * 5); // Header + buckets, entries, comparer, fields size += dict.Count * 4; // Bucket array (~4 bytes per entry) size += dict.Count * (4 + 4 + POINTER_SIZE * 2); // Entry array: hashcode, next, key, value foreach (object? key in dict.Keys) { size += EstimateSizeInternal (key, visited); size += EstimateSizeInternal (dict [key], visited); } return size; } private static long EstimateCollectionSize (object source, ConcurrentDictionary visited) { Type type = source.GetType (); long size = OBJECT_HEADER_SIZE + (POINTER_SIZE * 3); // Header + internal array + fields if (type.IsGenericType && type.GetGenericTypeDefinition () == typeof (Dictionary<,>)) { return EstimateDictionarySize (source, visited); } if (source is IEnumerable enumerable) { foreach (object? item in enumerable) { size += EstimateSizeInternal (item, visited); } } return size; } private static long EstimateObjectSize (object source, Type type, ConcurrentDictionary visited) { long size = OBJECT_HEADER_SIZE; // Size public writable properties foreach (PropertyInfo prop in type.GetProperties (BindingFlags.Instance | BindingFlags.Public) .Where (p => p is { CanRead: true, CanWrite: true } && p.GetIndexParameters ().Length == 0)) { try { object? value = prop.GetValue (source); size += EstimateSizeInternal (value, visited); } catch (Exception) { // // Log exception (enable for debugging) // Console.WriteLine($"Error processing property {prop.Name} of {type.FullName}: {ex.Message}"); // Continue to avoid crashing } } // For structs, also size fields (to handle generic structs) if (type.IsValueType) { FieldInfo [] fields = type.GetFields (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo field in fields) { try { object? fieldValue = field.GetValue (source); size += EstimateSizeInternal (fieldValue, visited); } catch (Exception) { // // Log exception (enable for debugging) // Console.WriteLine($"Error processing field {field.Name} of {type.FullName}: {ex.Message}"); // Continue to avoid crashing } } } return size; } private sealed class ReferenceEqualityComparer : IEqualityComparer { public static ReferenceEqualityComparer Instance { get; } = new (); public new bool Equals (object? x, object? y) { return ReferenceEquals (x, y); } public int GetHashCode (object obj) { return RuntimeHelpers.GetHashCode (obj); } } }