首页 > 解决方案 > C# Casting T where T: struct 到一个没有装箱的接口

问题描述

我需要编写一个无分配代码,以避免在从具有结构约束的通用参数 T 转换为访问一个已实现属性的接口时进行装箱。

我之前使用动态生成的代码和 lambda 表达式树解决了这个问题,例如:

        public delegate void SetEGIDWithoutBoxingActionCast<T>(ref T target, EGID egid) where T : struct, IEntityComponent;

        static SetEGIDWithoutBoxingActionCast<T> MakeSetter()
        {
            if (ComponentBuilder<T>.HAS_EGID)
            {
                Type         myTypeA     = typeof(T);
                PropertyInfo myFieldInfo = myTypeA.GetProperty("ID");

                ParameterExpression targetExp = Expression.Parameter(typeof(T).MakeByRefType(), "target");
                ParameterExpression valueExp  = Expression.Parameter(typeof(EGID), "value");
                MemberExpression    fieldExp  = Expression.Property(targetExp, myFieldInfo);
                BinaryExpression    assignExp = Expression.Assign(fieldExp, valueExp);

                var setter = Expression.Lambda<SetEGIDWithoutBoxingActionCast<T>>(assignExp, targetExp, valueExp).Compile();

                return setter;
            }

生成的“setter”委托将允许将属性值设置为任何实现ID属性的结构,而无需任何装箱。

但是现在我需要在不生成动态代码的情况下解决同样的问题。我做了一些快速的实验,但似乎Unsafe类无法使用任何As方法将结构转换为接口,可能是因为结构不能直接转换为接口,因为它被视为对象,因此无法处理直接用指针。

任何指针(没有双关语)?

编辑:我不一定需要将结构转换为接口,我只需要能够在属性 ID 中写入值。它是属性而不是字段的事实肯定没有帮助(因为我发现了一些能够计算结构中字段的偏移量的代码,但当然不能用于属性)

答案被接受,最终代码是这样的,它工作正常:

public delegate void SetEGIDWithoutBoxingActionCast<T>(ref T target, EGID egid) where T : struct, IEntityComponent;

static class SetEGIDWithoutBoxing<T> where T : struct, IEntityComponent
{
    public static readonly SetEGIDWithoutBoxingActionCast<T> SetIDWithoutBoxing = MakeSetter();

    public static void Warmup() { }

    static SetEGIDWithoutBoxingActionCast<T> MakeSetter()
    {
        if (ComponentBuilder<T>.HAS_EGID)
        {
            var method = typeof(Trick).GetMethod(nameof(Trick.SetEGIDImpl)).MakeGenericMethod(typeof(T));
            return (SetEGIDWithoutBoxingActionCast<T>) Delegate.CreateDelegate(
                typeof(SetEGIDWithoutBoxingActionCast<T>), method);
        }

        return null;
    }

    static class Trick
    {    
        public static void SetEGIDImpl<U>(ref U target, EGID egid) where U : struct, INeedEGID
        {
            target.ID = egid;
        }
    }
}

标签: c#unsafe

解决方案


没有装箱就不能将结构强制转换为接口。接口调用通过对象标头中的 vtable 工作,而未装箱的结构没有。

但是,我相信您尝试做的事情可以通过使用Delegate.CreateDelegate.

// No constraints needed on the delegate type
public delegate void SetEGIDWithoutBoxingActionCast<T>(ref T target, EGID egid);

public static SetEGIDWithoutBoxingActionCast<T> MakeSetter<T>()
{
    var method = typeof(Foo).GetMethod(nameof(Foo.SetEGIDImpl)).MakeGenericMethod(typeof(T));
    return (SetEGIDWithoutBoxingActionCast<T>)Delegate.CreateDelegate(typeof(SetEGIDWithoutBoxingActionCast<T>), method);
}

public static class Foo
{    
    public static void SetEGIDImpl<T>(ref T target, EGID egid) where T : INeedEGID
    {
        target.ID = egid;
    }
}

推荐阅读