Общий T с Enum и приведение T к Enum - PullRequest
5 голосов
/ 22 сентября 2011

Я искал вокруг и не мог найти никаких примеров, делающих это, хотя это было полезно:

Создать общий метод, ограничивающий T перечислением

У меня естьуниверсальная функция, которая оборачивает функцию в API (которую я не могу коснуться).Обернутая функция принимает System.Enum и возвращает то же самое.Моя универсальная версия несколько упрощает вещи в не урезанной версии этого примера.

Проблема в том, что я не могу перейти от T к System.Enum или обратно, поскольку T isn 't ограничен System.Enum (по крайней мере, это мое понимание).

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

using System
using System.Collections.Generic
...

    public T EnumWrapper<T>(T enumVar) where T : struct, IFormattable, IConvertible, IComparable
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException("Generic Type must be a System.Enum")

        // Use string parsing to get to an Enum and back out again
        Enum e = (Enum)Enum.Parse(typeof(T), enumVar.ToString());
        e = WrappedFunction(e);
        return (T)Enum.Parse(typeof(T), e.ToString());
    }

Если это нормально, то пусть это послужит примером.Я не мог найти это, и, по крайней мере, это обходной путь.

PS В этом случае производительность не является проблемой.Я думал, что струнная работа может быть медленной, и мне всегда интересны советы по производительности.

Ответы [ 2 ]

5 голосов
/ 22 сентября 2011

Вы можете сделать ограничение более жестким. Все перечисления реализуют следующие интерфейсы IFormattable, IConvertible и IComparable. Это в значительной степени ограничит вас примитивами, перечислениями и классами, которые ведут себя как они.

В качестве альтернативы, если вы действительно хотите создать метод или класс, привязываемый только к перечислениям, вы можете сделать следующее:

    public abstract class InvokerHelper<T> where T : class
    {
        public abstract R Invoke<R>(R value) where R : struct,T;
    }


        public class EnumInvoker : InvokerHelper<Enum>
        {
            public override R Invoke<R>(R value)
            {
                return (R)WrappedMethod(value);
            }
        }

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

Кроме того, вы можете написать свой код в C++/CLI или Reflection.Emit, что позволяет создавать классы, которые могут иметь ограничение where T:struct,Enum

На самом деле кажется, что вы просто хотите взять общий вызов метода T, который принимает перечисление и возвращает его как T правильно?

Тогда будет работать следующее.

public static T WrappedMethodInvoker<T>(T value) where T:struct,IComparable,IFormattable,IConvertible
{
    Enum e;
    if((e = value as Enum) == null) 
       throw new ArgumentException("value must be an Enum")
    object res = WrappedMethod(e);
    return (T)res;
}

Это немного проще. Во-первых, мы знаем, что T - это Enum, вы можете использовать оператор as, чтобы попытаться привести к типу NULL (ссылочный тип или структура NULL), что, безусловно, Enum. Оттуда мы можем использовать ковариацию возвращаемых типов, WrappedMethod возвращает Enum, что означает, что мы можем сохранить его в объекте. Наконец, когда у вас есть объект в универсальном методе, вам синтаксически разрешено привести его к T. Он может завершиться с ошибкой, но мы точно знаем, что метод возвращает Enum того же типа.

Существуют некоторые издержки, связанные с тем, что вы всегда занимаетесь боксом и распаковкой. Является ли обернутый метод универсальным?

Я отредактировал ответ, чтобы показать первую технику, исправленную в вашем примере. Он чрезвычайно прост и безопасен, но для выполнения действий всегда требуется EnumInvoker. К сожалению, оба ответа должны работать примерно одинаково, так как вызов WrappedMethod будет иметь значение, если его аргумент - Enum. Единственное преимущество первого метода состоит в том, что он строго типизирован, но общие виртуальные методы - самые медленные методы для вызова, насколько мне известно. Поэтому обход проверки типа может не стоить затрат на более простой вызов.

3 голосов
/ 22 сентября 2011

Здесь все просто: система типов C # не позволяет указывать ограничение для типа Enum.В коде нет очевидных ошибок, кроме того, он не вызовет ошибку времени компиляции, если вы передадите ему структуру IConvertible, которая не является Enum.В этом случае он потерпит неудачу во время выполнения, что нежелательно, но лучшего варианта нет.

Возможно, вы сможете применить это к CodeContracts во время компиляции, хотя я пока недостаточно знаю о CodeContracts (хотя я влюбился в них), чтобы дать вам определенный ответ здесь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...