runtime: Marshal.SizeOf(Type) is throwing incorrectly on generic types

Description

Marshal.SizeOf(typeof(somegenericstruct)) throws ArgumentException when T is a closed generic type. It should only throw on open generic types.

Here’s the implementation

public static int SizeOf(Type t)
        {
            if (t is null)
            {
                throw new ArgumentNullException(nameof(t));
            }
            if (!t.IsRuntimeImplemented())
            {
                throw new ArgumentException(SR.Argument_MustBeRuntimeType, nameof(t));
            }
            if (t.IsGenericType) // This should be changed to IsGenericTypeDefinition, I think
            {
                throw new ArgumentException(SR.Argument_NeedNonGenericType, nameof(t));
            }

            return SizeOfHelper(t, throwIfNotMarshalable: true);
        }

Configuration

Seen on .NET 5.0

Analysis

The documentation says that ArgumentException is thrown if t is a generic type definition. The implementation however throws when it is any generic type (including a closed one). The implementation of the similar method

public static int SizeOf<T>(T structure)

does not throw this exception - it can be used as workaround.

Here’s some test code:

    public class FrameworkBehaviorTests
    {
        [Fact]
        public void CannotGetSizeOfOpenGenericType()
        {
			// This exception is expected
            Assert.Throws<ArgumentException>(() =>
            {
                Marshal.SizeOf(typeof(GenericStruct<>));
            });
        }

        [Fact]
        public void CanGetSizeOfOpenGenericType()
        {
            // This test fails and throws as well
            Assert.Equal(8, Marshal.SizeOf(typeof(GenericStruct<short>)));
        }

        [Fact]
        public void CanGetSizeOfOpenGenericTypeViaInstance()
        {
			// This test passes, because it uses the SizeOf<T>(T object) overload
            GenericStruct<short> gs;
            gs._data1 = 2;
            gs._data2 = 10;
            Assert.Equal(8, Marshal.SizeOf(gs));
        }

        private struct GenericStruct<T>
        {
            public T _data1;
            public int _data2;
        }
    }

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 1
  • Comments: 17 (9 by maintainers)

Most upvoted comments

When generic interop types were made possible, it was really just relaxing the restriction in the interop logic so it could cross the managed/native boundary.

There wasn’t also work done to update Marshal.* to support this and much of that was done based on the feedback @AaronRobinsonMSFT has already given above.

Put another way, these types must already be blittable and so you don’t want to use any of the Marshal APIs. Unsafe.SizeOf, Unsafe.WriteUnaligned, NativeMemory.Alloc, and other APIs are all faster and better for blittable data. The same goes for directly taking/passing MyStruct<T>* and using pointer types directly.

This is all “inline” with an effort to move users off of the built-in marshalling support and onto things like DllImportGenerator which generates and works off blittable data behind the scenes.