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

этот вопрос уже есть ответ здесь:

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

Привет,

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

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 type (если я реализую его для этого.)

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

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

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

в C# обход

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

посмотреть:

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

5 ответов


EDIT: теперь доступна библиотека, поддерживающая это через ildasm / ilasm: UnconstrainedMelody.


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

наиболее практичным обходным путем в C# является:

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)
    {
        ...
    }
}

как упоминает греко, вы можете написать метод в C++ / CLI, а затем ссылаться на библиотеку классов из C# как на другую выбор.


есть работа-вокруг, что

посмотреть здесь


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

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 as Enum, чтобы предотвратить унаследованные версии для не-перечислений.


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

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

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

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

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

вы можете достичь этого, используя 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
{
    // ...
}