Created
March 23, 2021 13:28
-
-
Save ogxd/05c068c75050b0105c16601e8909b669 to your computer and use it in GitHub Desktop.
Measure size of an object through reflection
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Reflection; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
using System.Threading; | |
namespace System.Reflection | |
{ | |
public static class SizeOfExtensions | |
{ | |
public class ReferenceComparer : IEqualityComparer, IEqualityComparer<object> | |
{ | |
public new bool Equals(object x, object y) | |
{ | |
return object.ReferenceEquals(x, y); | |
} | |
public int GetHashCode(object obj) | |
{ | |
return RuntimeHelpers.GetHashCode(obj); | |
} | |
} | |
private static Dictionary<Type, FieldInfo[]> _cachedFields = new Dictionary<Type, FieldInfo[]>(); | |
private static Dictionary<Type, int> _cachedSizes = new Dictionary<Type, int>(); | |
public static long GetSizeInBytes(this object obj, CancellationToken ct) | |
{ | |
HashSet<object> references = new HashSet<object>(new ReferenceComparer()); | |
Stack<object> objects = new Stack<object>(); | |
objects.Push(obj); | |
long size = 0; | |
while (objects.Count > 0) | |
{ | |
if (ct.IsCancellationRequested) | |
return -2; // Timeout | |
object current = objects.Pop(); | |
var type = current.GetType(); | |
switch (Type.GetTypeCode(type)) | |
{ | |
case TypeCode.Empty: | |
size += IntPtr.Size; | |
break; | |
case TypeCode.Boolean: | |
case TypeCode.Byte: | |
case TypeCode.SByte: | |
size += sizeof(byte); | |
break; | |
case TypeCode.Char: | |
size += sizeof(char); | |
break; | |
case TypeCode.Int16: | |
case TypeCode.UInt16: | |
size += sizeof(short); | |
break; | |
case TypeCode.Single: | |
case TypeCode.Int32: // Enums fall here | |
case TypeCode.UInt32: | |
size += sizeof(float); | |
break; | |
case TypeCode.Double: | |
case TypeCode.Int64: | |
case TypeCode.UInt64: | |
size += sizeof(double); | |
break; | |
case TypeCode.Decimal: | |
case TypeCode.DateTime: | |
size += sizeof(decimal); | |
break; | |
case TypeCode.String: | |
size += IntPtr.Size; | |
if (references.Add(current)) | |
size += sizeof(char) * (current as string).Length; | |
break; | |
case TypeCode.Object: | |
{ | |
if (type.IsValueType) | |
{ | |
// Optim for blittable types | |
int tsize = GetSize(type); | |
if (tsize > 0) | |
{ | |
size += tsize; | |
continue; | |
} | |
if (!_cachedFields.TryGetValue(type, out FieldInfo[] fields)) | |
{ | |
fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); | |
_cachedFields.Add(type, fields); | |
} | |
for (int i = 0; i < fields.Length; i++) | |
{ | |
var tempVal = fields[i].GetValue(current); | |
if (tempVal != null) | |
{ | |
objects.Push(tempVal); | |
} | |
else | |
{ | |
size += IntPtr.Size; | |
} | |
} | |
} | |
else | |
{ | |
if (!references.Add(current)) | |
continue; | |
if (type.IsArray) | |
{ | |
size += IntPtr.Size; | |
var casted = (Array)current; | |
var elemType = type.GetElementType(); | |
// Optim for blittable element types | |
int tsize = GetSize(elemType); | |
if (tsize > 0) | |
{ | |
size += tsize * casted.Length; | |
continue; | |
} | |
foreach (var item in casted) | |
{ | |
if (item != null) | |
{ | |
objects.Push(item); | |
} | |
else | |
{ | |
size += IntPtr.Size; | |
} | |
} | |
} | |
else | |
{ | |
while (type != null) | |
{ | |
size += IntPtr.Size; | |
if (!_cachedFields.TryGetValue(type, out FieldInfo[] fields)) | |
{ | |
fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); | |
_cachedFields.Add(type, fields); | |
} | |
for (int i = 0; i < fields.Length; i++) | |
{ | |
var tempVal = fields[i].GetValue(current); | |
if (tempVal != null) | |
{ | |
objects.Push(tempVal); | |
} | |
else | |
{ | |
size += IntPtr.Size; | |
} | |
} | |
type = type.BaseType; | |
} | |
} | |
} | |
} | |
break; | |
default: | |
break; | |
//throw new NotSupportedException($"Typecode '{Type.GetTypeCode(type)}' is not supported !"); | |
} | |
} | |
return size; | |
} | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static int GetSize(Type type) | |
{ | |
if (!_cachedSizes.TryGetValue(type, out int tsize)) | |
{ | |
try | |
{ | |
tsize = Marshal.SizeOf(type); | |
} | |
catch { } | |
_cachedSizes.Add(type, tsize); | |
} | |
return tsize; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment