See the question and my original answer on StackOverflow

The Unsafe class has no knowledge of P/Invoke rules, only the Marshal class does.

For example, if you take this little program:

unsafe internal class Program
    static void Main()
        var info = new MyStruct();
        Console.WriteLine(Marshal.SizeOf<MyStruct>()); // 8
        Console.WriteLine(Unsafe.SizeOf<MyStruct>()); // 16
        info.SomeValue = 0x12345678;
        info.Name = "Test";
        var p = Marshal.AllocCoTaskMem(100);
        var ptr = p.ToPointer();
        Unsafe.Write(ptr, info);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MyStruct
        public uint SomeValue;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)] // includes terminating zero
        public string Name;

And debug it with a breakpoint after the Unsafe.Write method, open a memory window and use ptr as the start address, and you'll see this:

enter image description here

The SomeValue member is visible, but the Name is not, because ptr points to the managed object (with a probable leading pointer to something internal), not to it's P/invoke "projection".

Also, note in this case Marshal.SizeOf (P/Invoke size = 8) and Unsafe.SizeOf (Managed size = 16) results are different.