Para tipos não gerenciados, também conhecidos como tipos de valor, structs:
Marshal.SizeOf(object);
Para objetos gerenciados, quanto mais perto eu chego, é uma aproximação.
long start_mem = GC.GetTotalMemory(true);
aclass[] array = new aclass[1000000];
for (int n = 0; n < 1000000; n++)
array[n] = new aclass();
double used_mem_median = (GC.GetTotalMemory(false) - start_mem)/1000000D;
Não use serialização. Um formatador binário adiciona cabeçalhos, para que você possa alterar sua classe e carregar um arquivo serializado antigo na classe modificada.
Também não informa o tamanho real da memória nem leva em consideração o alinhamento da memória.
[Edit] Usando BiteConverter.GetBytes (prop-value) recursivamente em cada propriedade de sua classe, você obteria o conteúdo em bytes, que não conta o peso da classe ou referências, mas está muito mais próximo da realidade. Eu recomendaria usar uma matriz de bytes para dados e uma classe de proxy não gerenciada para acessar valores usando projeção de ponteiro se o tamanho for importante, observe que seria memória não alinhada, então em computadores antigos será lento, mas conjuntos de dados ENORMES na RAM MODERNA serão consideravelmente mais rápido, pois minimizar o tamanho para ler da RAM terá um impacto maior do que o desalinhado.