Because "real" memory isn't organised in bytes: it's organised in units of the processor / OS size: 32 bits or 64 bits.
And a datatype must start on it's "natural" boundary: for a byte, the address can end with anything. For a short (16 bits) it must end with a binary zero. A double (which needs 64 bits, or 8 bytes, wide) needs to start on a memory location that ends with binary 000 in order to be accessed within it's natural boundary.
So when you put an int in front of it, the int is "padded" to allow teh double to hit teh natural boundary.
Try it: put another int in between:
[StructLayout(LayoutKind.Sequential)]
class test
{
int i;
double d;
}
[StructLayout(LayoutKind.Sequential)]
class test2
{
int i;
int j;
double d;
}
You'll find they are both the same size, while this:
[StructLayout(LayoutKind.Sequential)]
class test3
{
int i;
double d;
int j;
}
takes another 8 bytes.