Я хочу реализовать следующий метод, используя .NET Standard:
public static void SetFlag<TEnum>(ref TEnum value, TEnum flag)
where TEnum : Enum
Я провожу часы, пытаясь добиться этого:
| через отражение кажется невозможным для примитивных типов, таких как enums.dynamic требует ссылки на дополнительный пакет (Microsoft.CSharp.RuntimeBinder), но я бы хотел, чтобы моя библиотека оставалась чистой в соответствии со стандартом .NET.Моя последняя идея заключалась в том, чтобы вручную сравнитьTEnum для каждого допустимого типа перечисления {byte, sbyte, short, ushort, int, uint, long, ulong}. Но это кажется действительно странным и грязным:
try
{
var v = (byte)(object)value | (byte)(object)flag;
value = (TEnum)(object)v;
return;
}
catch (InvalidCastException) { }
try
{
var v = (int)(object)value | (int)(object)flag;
value = (TEnum)(object)v;
return;
}
catch (InvalidCastException) { }
// ...
throw new NotSupportException($"Unknown enum type {typeof(TEnum)}");
Так это действительно единственный вариант, который .NET (Standard) предоставляет здесь, или что мне не хватает? Ждем ваших подсказок!
Редактировать: Не является дубликатом этот вопрос; Я использую C# 7.3 и общее ограничение Enum.
Да, я только что понял, что - я удалил свой комментарий!
@KnorxThieus - да, извините. Я полностью ожидал, что это возможно.
Возможный дубликат Метод С# для объединения общего списка значений перечисления в одно значение
Дубликат, который я предложил, имеет функционирующие универсальные методы для выполнения различных побитовых операций над перечислениями любого типа и базового размера без упаковки. См. ответ доктора Джонса (который оптимизирует мое решение).
Кстати, я думаю, вы хотите, чтобы ваше ограничение было struct, Enum, иначе вы не сможете использовать его просто с любым старым перечислением, а только с фактическим типом Enum
@pinkfloydx33 Вы также не можете вызывать | на structs.





Это не самое дешевое (все упаковано, есть отражение и т. д.), но вы всегда можете сделать что-то вроде этого:
private static void SetFlag<T>(ref T value, T flag) where T : Enum
{
// 'long' can hold all possible values, except those which 'ulong' can hold.
if (Enum.GetUnderlyingType(typeof(T)) == typeof(ulong))
{
ulong numericValue = Convert.ToUInt64(value);
numericValue |= Convert.ToUInt64(flag);
value = (T)Enum.ToObject(typeof(T), numericValue);
}
else
{
long numericValue = Convert.ToInt64(value);
numericValue |= Convert.ToInt64(flag);
value = (T)Enum.ToObject(typeof(T), numericValue);
}
}
У вас все еще есть повторения, но, по крайней мере, они ограничены long/ulong. Если вы можете предположить, что члены перечисления ваших флагов не будут иметь отрицательных значений, вы можете просто использовать:
private static void SetFlag<T>(ref T value, T flag) where T : Enum
{
ulong numericValue = Convert.ToUInt64(value);
numericValue |= Convert.ToUInt64(flag);
value = (T)Enum.ToObject(typeof(T), numericValue);
}
Спасибо за заявку. Почему именно это решение вы бы предпочли моему предложению выше? Какой подход должен быть быстрее?
Почти наверняка будет быстрее. Генерация исключения обходится очень дорого (в относительном выражении), и ваш подход может генерировать довольно много исключений, прежде чем он найдет правильный тип (особенно если вы начинаете с byte). По крайней мере, я бы проверил Enum.GetUnderlyingType(), чтобы не рисковать получить исключение. Кроме того, я обычно следую правилу, согласно которому меньше повторений кода — лучше. Не забывайте, что ваш подход также много боксирует.
Спасибо. Интересно, что даже Microsoft в своей реализации Enum не различает ulong и Co. [1] referencesource.microsoft.com/#mscorlib/system/enum.cs, 158
@Corak Я так не думаю, поскольку использую ограничение
System.Enum.