Подтвердить что ты не робот

Есть ли обходной путь для ограничения общего типа "специальный класс" Enum в С# 3.0?

Обновление: См. нижнюю часть этого вопроса для обходного пути С#.

Привет,

Рассмотрим следующий метод расширения:

public static bool HasFlags<T>(this T value, T flags)
    where T : System.Enum
{
    // ...
}

Это, как вы, возможно, знаете, выкидывает ошибку во время компиляции, так как классу обычно не разрешается наследовать от System.Enum. Проблема в том, что любое перечисление, указанное с использованием ключевого слова enum, действительно наследует от System.Enum, поэтому приведенный выше код был бы идеальным способом ограничить метод расширения только перечислениями.

Теперь очевидно, что здесь нужно использовать enum вместо T, но затем вы теряете преимущества родовых типов:

MyEnum e;
e.HasFlags(MyOtherEnum.DoFunkyStuff);

Вышеприведенный код будет генерировать ошибку времени компиляции с использованием общих типов, в то время как он может вызывать только ошибку времени выполнения, используя тип enum (если я его реализую).

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

Прежде чем это будет предложено, я хотел бы сказать, что я не буду использовать where T : struct или некоторые такие, так как тогда вы сможете делать такие странные вещи, как 123.HasFlags(456).

Я в тупике, почему эта ошибка существует вообще... Это та же проблема, с которой вы могли бы воспользоваться where T : System.Object, но для этого у вас есть where T : class... Почему нет where T : enum?

Обходное решение С#

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

См:

Если это обходное решение неосуществимо, вам придется написать свою библиотеку как код С++/CLI, который не ограничивает того, что можно использовать для ограничений типа generic (см. код в моем ответе ниже.)

4b9b3361

Ответ 1

EDIT: теперь доступна библиотека с поддержкой ildasm/ilasm: UnconstrainedMelody.


Члены команды С# ранее заявили, что хотели бы поддерживать where T : Enum и where T : Delegate, но это никогда не было достаточно высоким приоритетом. (Я не уверен, что аргументы в пользу того, что ограничение в первую очередь, по общему признанию...)

Наиболее практичным решением в С# является:

public static bool HasFlags<T>(this T value, T flags) where T : struct
{
    if (!(value is Enum))
    {
        throw new ArgumentException();
    }
    // ...
}

Это теряет проверку времени компиляции для "перечисления", но сохраняет проверку того, что вы используете один и тот же тип в обоих местах. Разумеется, у него также есть штраф за выполнение чека. Вы можете избежать этого времени исполнения после первого вызова, используя общий вложенный тип для реализации, который генерирует исключение в статическом конструкторе:

public static bool HasFlags<T>(this T value, T flags) where T : struct
{
    if (!(value is Enum))
    {
        throw new ArgumentException();
    }
    return EnumHelper<T>.HasFlags(value, flags);
}

private class EnumHelper<T> where T : struct
{
    static EnumHelper()
    {
        if (!typeof(Enum).IsAssignableFrom(typeof(T))
        {
            throw new InvalidOperationException(); // Or something similar
        }
    }

    internal static HasFlags(T value, T flags)
    {
        ...
    }
}

Как упоминает Greco, вы можете написать метод в С++/CLI, а затем ссылаться на библиотеку классов из С# в качестве другой опции.

Ответ 3

Собственно, это возможно, с уродливым трюком. Однако он не может использоваться для методов расширения.

public abstract class Enums<Temp> where Temp : class {
    public static TEnum Parse<TEnum>(string name) where TEnum : struct, Temp {
        return (TEnum)Enum.Parse(typeof(TEnum), name); 
    }
}
public abstract class Enums : Enums<Enum> { }

Enums.Parse<DateTimeKind>("Local")

Если вы хотите, вы можете предоставить Enums<Temp> частный конструктор и открытый вложенный абстрактный унаследованный класс с Temp как Enum, чтобы предотвратить унаследованные версии для не-перечислений.

Ответ 4

Я не мог удержаться от работы на С++, и, поскольку я получил его на работу, я решил, что поделился бы им с остальной частью ya!

Здесь код С++ (мой С++ очень ржавый, поэтому, пожалуйста, укажите любые ошибки, в частности, как определяются аргументы):

#include "stdafx.h"

using namespace System;
using namespace System::Runtime::CompilerServices;

namespace Blixt
{
namespace Utilities
{
    [Extension]
    public ref class EnumUtility abstract sealed
    {
    public:
        generic <typename T> where T : value class, Enum
        [Extension]
        static bool HasFlags(T value, T flags)
        {
            __int64 mask = Convert::ToInt64(flags);
            return (Convert::ToInt64(value) & mask) == mask;
        }
    };
}
}

И код С# для тестирования (консольное приложение):

using System;
using Blixt.Utilities;

namespace Blixt.Playground
{
    [Flags]
    public enum Colors : byte
    {
        Black = 0,
        Red = 1,
        Green = 2,
        Blue = 4
    }

    [Flags]
    public enum Tastes : byte
    {
        Nothing = 0,
        Sour = 1,
        Sweet = 2,
        Bitter = 4,
        Salty = 8
    }

    class Program
    {
        static void Main(string[] args)
        {
            Colors c = Colors.Blue | Colors.Red;
            Console.WriteLine("Green and blue? {0}", c.HasFlags(Colors.Green | Colors.Red));
            Console.WriteLine("Blue?           {0}", c.HasFlags(Colors.Blue));
            Console.WriteLine("Green?          {0}", c.HasFlags(Colors.Green));
            Console.WriteLine("Red and blue?   {0}", c.HasFlags(Colors.Red | Colors.Blue));

            // Compilation error:
            //Console.WriteLine("Sour?           {0}", c.HasFlags(Tastes.Sour));

            Console.WriteLine("Press any key to exit...");
            Console.ReadKey(true);
        }
    }
}

Ответ 5

Вы можете добиться этого, используя IL Weaving и ExtraConstraints

Позволяет вам написать этот код

public static bool HasFlags<[EnumConstraint] T>(this T value, T flags)
{
    // ...
} 

Что скомпилировано

public static bool HasFlags<T>(this T value, T flags)
    where T : System.Enum
{
    // ...
}