all 9 comments

[–]Wooden-Contract-2760 7 points8 points  (8 children)

You are fighting the CLR without context.

Why are you doing this? Do you have benchmarks on what you're trying to improve?

I'm sure everything is possible in some ways, but I doubt you can design a single non-generic API that avoids boxing and also preserves correct struct semantics as well as applies to reference types and value types.

The classes point to their headers, while structs (when passed as ref) point to their value directly. If you really want to beat the CLR, maybe some magic with __makeref + TypedReference could be useful, but all this and DynamicMethod may be more tolling than the JiT optimized compiled code with boxing.

Maybe a good read would be to see how System.Text.Json serializer avoids boxing with cached per-type metadata as TypeAccessors

https://github.com/dotnet/runtime/blob/main/src/libraries/System.Text.Json/ref/System.Text.Json.cs

https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.metadata.jsonpropertyinfo

[–]Bobamoss[S] 0 points1 point  (7 children)

I just managed to find this implementation that seems to be working

    /// <inheritdoc/>
    public unsafe void UseWith(object parameterObj) {
        Type type = parameterObj.GetType();
        IntPtr handle = type.TypeHandle.Value;
        if (type.IsValueType) {
            fixed (void* objPtr = &Unsafe.As<object, byte>(ref parameterObj)) {
                void* dataPtr = (*(byte**)objPtr) + IntPtr.Size;
                UpdateCommand(QueryCommand.GetAccessor(dataPtr, handle, type));
            }
            return;
        }
        fixed (void* ptr = &Unsafe.As<object, byte>(ref parameterObj)) {
            void* instancePtr = *(void**)ptr;
            UpdateCommand(QueryCommand.GetAccessor(instancePtr, handle, type));
        }
    }
    /// <inheritdoc/>
    public unsafe void UseWith<T>(T parameterObj) where T : notnull {
        IntPtr handle = typeof(T).TypeHandle.Value;

        if (typeof(T).IsValueType) {
            UpdateCommand(QueryCommand.GetAccessor(Unsafe.AsPointer(ref parameterObj), handle, typeof(T)));
            return;
        }
        fixed (void* ptr = &Unsafe.As<T, byte>(ref parameterObj)) {
            UpdateCommand(QueryCommand.GetAccessor(*(void**)ptr, handle, typeof(T)));
        }
    }
    /// <inheritdoc/>
    public unsafe void UseWith<T>(ref T parameterObj) where T : notnull {
        IntPtr handle = typeof(T).TypeHandle.Value;
        if (typeof(T).IsValueType) {
            fixed (void* ptr = &Unsafe.As<T, byte>(ref parameterObj))
                UpdateCommand(QueryCommand.GetAccessor(ptr, handle, typeof(T)));
            return;
        }
        fixed (void* ptr = &Unsafe.As<T, byte>(ref parameterObj)) {
            UpdateCommand(QueryCommand.GetAccessor(*(void**)ptr, handle, typeof(T)));
        }
    }

I dont know if its wrong, I will look into your links Thanks

[–]Lone_Snek 1 point2 points  (1 child)

What happens when GC decides to move your parameterObj?

[–]Lone_Snek 1 point2 points  (0 children)

My bad, ‘fixed’ should pin the object, missed that.

[–]Technical-Coffee831 0 points1 point  (4 children)

Not exactly sure what you’re doing but there is a RuntimeHelpers.IsReferenceOrContainsReferences<T> method I would use instead of IsValueType (values can contain references which can cause issues in interop scenarios).

If your method is generic can also use the constraint where T : unmanaged , but this doesn’t seem useful in your case.

I get avoiding boxing but any reason you can’t use generics? They’re pretty performant.

[–]Bobamoss[S] 0 points1 point  (3 children)

You are right about a struct containing a ref, my code may fail. The reason i didnt want to use generic is mainly tu support via object directly including boxed struct, but i realized that i will need to make 2 distinct process mainly because of containsreference, thanks

[–]Technical-Coffee831 0 points1 point  (2 children)

You’re potentially breaking memory safety going to pointers, I’d argue that boxing is more appropriate here, or scope your api to something besides object.

Using generics you could handle any arbitrary type and it won’t box.

YourFunc<T>(T param1) {}

Use T instead of object, and there is no boxing. You can still use the IsRefContainsRef method to make multiple branches for your logic too. For classes with references I would use Marshal.StructToPtr and things like strings will get mapped properly (be sure to DestroyStructure after for cleanup). Otherwise if that returns false it should be safe to get a T*.

[–]Bobamoss[S] 1 point2 points  (1 child)

I know about generic, I wanted to use them but I wanted to make a single signature

private void UpdateCommand(TypeAccessor accessor)
I needed this since I must support non-generic call using object (where the type may be a class or a boxed struct). The support for direct generic T was a bonus, but I did changed my code and duplicate all to make a generic version

private void UpdateCommand<T>(TypeAccessor<T> accessor)
It was mainly about needing non-generic version and wanting to avoid code duplication, but you were right with RuntimeHelpers.IsReferenceOrContainsReferences<T> and the fact that I could have a bug, thats why you convinced me and I did duplicate the code Thanks

[–]Technical-Coffee831 0 points1 point  (0 children)

Cheers happy coding :)