Существует ли обходной путь для ограничения универсального типа перечисления "специальный класс" В 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
после построения. Это, я считаю, самое близкое, что можно сделать, чтобы обойти это вопрос на этот раз.посмотреть:
- код проекта:http://code.google.com/p/unconstrained-melody/
- запись в блоге: http://msmvps.com/blogs/jon_skeet/archive/2009/09/10/generic-constraints-for-enums-and-delegates.aspx
если этот обходной путь неосуществим, вам придется написать свою библиотеку как код 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
{
// ...
}