Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Add sizeof(T) support for unmanaged constraint #1508

Closed
dangi12012 opened this issue May 9, 2018 · 18 comments
Closed

[Proposal] Add sizeof(T) support for unmanaged constraint #1508

dangi12012 opened this issue May 9, 2018 · 18 comments

Comments

@dangi12012
Copy link

dangi12012 commented May 9, 2018

If I use the unmanaged constraint on a generic type parameter the underlying type cannot contain any reference type. Ergo the size is known.
Can we have support for this:

int Elementsize = Marshal.SizeOf<T>(); //Before

int Elementsize = sizeof(T); //Should work for -> where T : unmanaged

This would make Marshal and unsafe unnecessary for much Pinvoke Code. Which is a very good thing for speed.

@omariom
Copy link

omariom commented May 10, 2018

I think you will still have to use Marshal.SizeOf for Pinvoke. Size for marshaling purposes can be different from what sizeof IL instruction returns.

struct ValueStruct { bool b1,b2,b3,b4; }

Unsafe.SizeOf<ValueStruct>().Dump();
Marshal.SizeOf<ValueStruct>().Dump();
4
16

@Korporal
Copy link

@dangi12012 @omariom - If you are marshaling a struct then you must use the Marshal.SizeOf - that is what it's intended for. However I agree that being able to get the physical size in managed memory might be useful for some situations (I have done this before and wrote utility code to determine this at runtime).

However @dangi12012 your post is unclear because sizeof(T) does actually compile and work...

    public class Me<T> where T : unmanaged
    {
        public unsafe Me()
        {
            int x = sizeof(T);
        }
    }

@dangi12012
Copy link
Author

Thats right. I need the size of the struct inside Memory. I dont plan to marshal it for C++. With Span developers really get a powerful tool which can be used in a safe manner. sizeof(T) should work with unmanaged keyword because the struct size can be determined at compile time and is blittable (No reference Types).

//Comile error sizeof(T) can only be determined in an unsafe context.
public class Me<T> where T : unmanaged
    {
        public unsafe Me()
        {
            int x = sizeof(T);
        }
    }

@ufcpp
Copy link

ufcpp commented May 16, 2018

//Comile error sizeof(T) can only be determined in an unsafe context.

Really? That code can be compiled without any error.

Do you want this to be compilable without unsafe context?

@dangi12012
Copy link
Author

Yes. This should be compilable without the use of the unsafe context. Span works in a safe context, so sizeof(T) where T : unmanaged would speed up things AND is safe after all.
I really was confused why this is not the case since unmanaged => blittable => we know the size.

@nguerrera
Copy link

nguerrera commented May 20, 2018

should work with unmanaged keyword because the struct size can be determined at compile time

For arbitrary struct T, the size will only be known at runtime. For example, consider IntPtr fields, auto layout, and CLR implementation-defined padding. (Or did you mean JIT compile time?)

Do you want this to be compilable without unsafe context?

IMHO, it should work without unsafe context. It should even work without the unmanaged constraint. I have never understood why sizeof only works for unmanaged types or why it requires unsafe context for anything but the fixed size primitives. The sizeof IL instruction is always verifiable and perfectly safe.

@xoofx
Copy link
Member

xoofx commented Aug 21, 2018

Could we proceed with this and make sizeof(T) available once and for good in C# as we can at the IL level without consideration of whether it is a struct/object ref/primitive type, safe/unsafe or unmanaged constraint? Currently we have to roll out our own IL rewriter (e.g like in SharpDX) or use the System.Runtime.CompilerServices.Unsafe.Unsafe.SizeOf<T>() to be able to perform this simple operation. Afaik, this is only a matter of unlocking this in Roslyn, so is there any objections to not unlock this?

@tannergooding
Copy link
Member

@xoofx, I would recommend creating an individual issue requesting the change.

@Korporal
Copy link

Korporal commented Aug 31, 2018

@xoofx - An alternative is to write a method that dynamically generates a delegate from IL instructions. That IL delegate is an algorithm that creates an instance of T and then determines the difference between the instance's address and the address of its highest offset member field. It then adds the length of that field.

This reliably determines the physical size of 'T' as its defined in the managed environment.

By making this a static operation with some form of dictionary, the above is done once and thereafter the cached size (keyed on typeof(T)) is then instantly available with almost no overhead at runtime.

I used this to great effect once.

@xoofx
Copy link
Member

xoofx commented Aug 31, 2018

@xoofx, I would recommend creating an individual issue requesting the change.

Will do

An alternative is to write a method that dynamically generates a delegate from IL instructions.

Hm, sorry, I'm not sure to follow. We have already sizeof at the IL level, we can expose it and we are already using it in C#. We are just asking to make this sizeof operator fully available in C# so that we don't have to rely on an external IL lib to "un-restrict" it.

The sizeof operator is important at IL level because it boils down to a compile time constant (at JIT or AOT time), so not sure why we would go to a more complicated route with allocations, a dictionary, locking...etc (note that you could make it without a dictionary actually with a static readonly field), just to get an information that is straightforward to get, even today, but alas, not integrated by default in the language, so we have to rely on an external/IL compiled lib (like System.Runtime.CompilerServices.Unsafe.Unsafe.SizeOf<T>())

@dangi12012
Copy link
Author

Well then implement it please.
This would go hand in hand with Span and the new classes of blittable types and unmanaged keyword.

@jeffvella
Copy link

It would be nice if these worked:

    public struct MyStruct1<T> where T : unmanaged
    {
        public const int Size = sizeof(T);
    }

    public unsafe struct MyStruct2<T> where T : unmanaged
    {
        private fixed T _buffer[4];
    }

@tannergooding
Copy link
Member

@imxzjv, The first scenario can't work because sizeof(T) is not a constant for anything but primitive types and cannot be constant even for T, since T is not concrete and sizeof(T) varies based on the actual underlying T.

The runtime will treat Unsafe.SizeOf<T>() and sizeof(T) as runtime constants, however, and will do all the normal constant propagation, folding, etc.

The latter can't work because fixed sized buffers are constrained to a limited set of primitive type and would require a new feature (such as the already championed proposal: #1314) to extend that to support any type and be useable in "safe" code (relying on the ref return fe as true to work and relying on explicitly declared structs for type layout and sizing to be correct).

@dangi12012
Copy link
Author

dangi12012 commented Feb 6, 2019

2- would work with a new constraint:
where T: ValueType //meaning byte, sbyte, short and so on (basically what it says in intellisense for allowed fixed buffer types) would also mean IConvertible, IComparable, IFormattable and every other interface the basic C# types have.

1- works for static readonly but can never ever work for const. This compiles fine and static readonly is the same as const as far as the jit is concerned.

public unsafe struct MyStruct1<T> where T : unmanaged
    {
        public static readonly int Size = sizeof(T);
    }

If my proposal were implemented you could drop the unsafe. Since the struct is safe you can see how my proposal makes sense for T : unmanaged

@Joe4evr
Copy link
Contributor

Joe4evr commented Feb 7, 2019

2- would work with a new constraint:
where T: ValueType //meaning byte, sbyte, short and so on

....and literally every single struct type made in all of .NET ever? Sure, it's illegal now, but having a ValueType constraint that behaves such that only the core primitives are allowed is gonna be mighty confusing.

@mikernet
Copy link

mikernet commented Nov 22, 2019

@tannergooding Here is an example of where sizeof(T) and Unsafe.SizeOf<T>() produce different JITted output, so it appears as though there are some differences in how the runtime treats both. It appears that this particular case is related to how early the runtime can eliminate branches with each approach.

I think sizeof should just be allowed everywhere, regardless of struct/unmanaged/class/etc. If there are concerns about using it in certain places then worst case it should require an unsafe context but otherwise we should have full access to use it wherever we want/need to. I don't see any situation where Unsafe.SizeOf<T>() is better than just allowing sizeof in an unsafe context.

@YairHalberstadt
Copy link
Contributor

Related is the proposal championed at #1828, which was rejected:

Allow sizeof in safe code

There have been requests for this over the years, but we're not sure of the actual value of the feature.

##Conclusion

Rejected, until we hear compelling use cases.

@jnm2
Copy link
Contributor

jnm2 commented Oct 15, 2020

It would make my code nicer when passing sizeof(SomeBlittableStruct) to a p/invoke parameter in a signature that has only managed types, or when setting the size field of a blittable struct (again in an otherwise safe context).

An example from last week is https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerrawinputdevices where I need to pass sizeof(RAWINPUTDEVICE) alongside the managed array that I'm passing. My declaration of RAWINPUTDEVICE is blittable. (I'm using a managed array in the p/invoke signature because I want the marshaler to generate the pinning boilerplate for me.)

@dotnet dotnet locked and limited conversation to collaborators Dec 3, 2024
@333fred 333fred converted this issue into discussion #8750 Dec 3, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests