C# Приведение T, где T: структура интерфейса без бокса - PullRequest
0 голосов
/ 13 апреля 2020

Мне нужно написать свободный код для выделения, который позволяет избежать упаковки при приведении из универсального c параметра T со структурными ограничениями к интерфейсу для доступа к одному реализованному свойству.

Ранее я решил эту проблему, используя динамически сгенерированный код и дерево лямбда-выражений, например:

        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;
            }

Полученный в результате делегат "установщика" позволит установить значение свойства для любой структуры, реализующей свойство ID без каких-либо упаковок.

Однако теперь мне нужно решить ту же проблему без генерации динамического c кода. Я провел несколько быстрых экспериментов, но кажется, что Небезопасный класс не может преобразовать структуру в интерфейс, используя любой из As методов. Возможно, потому что структура не может быть приведена непосредственно к интерфейсу, поскольку она рассматривается как объект, и, следовательно, не может обрабатываться непосредственно указателем.

Любой указатель (без каламбура)?

Править : Мне не обязательно приводить структуру к интерфейсу, мне просто нужно иметь возможность записать значение в идентификатор свойства. Тот факт, что это свойство, а не поле, не поможет наверняка (поскольку я нашел некоторый код, который может вычислить смещение поля в структуре, но, конечно, не может использоваться для свойства)

Ответ принят, окончательный код такой, и он отлично работает:

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;
        }
    }
}

1 Ответ

1 голос
/ 13 апреля 2020

Вы не можете привести структуру к интерфейсу без бокса. Вызовы интерфейса работают через виртуальную таблицу, найденную в заголовке объекта, которого нет в распакованных структурах.

Однако я считаю, что то, что вы пытаетесь сделать, может быть достигнуто путем создания неограниченного делегата для ограниченного обобщенного кода c метод с использованием 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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...