Skip to content

Instantly share code, notes, and snippets.

@ogxd
Created March 23, 2021 13:28
Show Gist options
  • Save ogxd/05c068c75050b0105c16601e8909b669 to your computer and use it in GitHub Desktop.
Save ogxd/05c068c75050b0105c16601e8909b669 to your computer and use it in GitHub Desktop.
Measure size of an object through reflection
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