Skip to content

Instantly share code, notes, and snippets.

@xepherys
Last active November 9, 2019 13:33
Show Gist options
  • Save xepherys/4f79bd63c23332a83ed1e4d8b22c532a to your computer and use it in GitHub Desktop.
Save xepherys/4f79bd63c23332a83ed1e4d8b22c532a to your computer and use it in GitHub Desktop.
Rotating values of a flagged System.Enum in C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
// Requires C# 7.3 or later (Roslyn 3.0 or later) for where TEnum : System.Enum
public class Program
{
public static void Main()
{
CompassDirections dirs = CompassDirections.NORTH | CompassDirections.SOUTHEAST;
#region Test functionality
Console.WriteLine("Valid directions are: {0}\n", dirs);
// Rotation as a standard method
dirs = Rotate(dirs, 2);
Console.WriteLine("Updated directions are: {0}\n", dirs);
// Rotation as an extension method to TEnum
dirs.Rotate(6);
Console.WriteLine("Updated directions are: {0}\n", dirs);
#endregion
}
// Standard method
public static TEnum Rotate<TEnum>(TEnum inEnum, int positions = 1) where TEnum : System.Enum
{
if (positions == 0)
{
Console.WriteLine("Rotate<TEnum>() called to rotate 0 positions. No rotation will occur.");
return inEnum;
}
// Currently doesn't support any underlying enum type but int
if (Enum.GetUnderlyingType(inEnum.GetType()) != typeof(int))
throw new NotSupportedException("This method, Rotate<TEnum>(), is only valud on integer underlying types. The System,Enum '" + inEnum.GetType().Name + "' does not use such an underlying value.");
if (!typeof(TEnum).GetCustomAttributes<FlagsAttribute>().Any())
throw new NotSupportedException("This method, Rotate<TEnum>(), is only valid for enums that have the [System.Flags] attribute set. The System.Enum '" + inEnum.GetType().Name + "' does not use that attribute.");
List<int> values = new List<int>((int[])Enum.GetValues(inEnum.GetType()));
if (values.Any(a => a < 0))
throw new NotSupportedException("This method, Rotate<TEnum>(), is only valid for enums that contain no negative integer values. The System.Enum '" + inEnum.GetType().Name + "' includes negative values.");
if (((int)(object)inEnum ^ 0) == 0)
{
Console.WriteLine("Rotate<TEnum>() called to rotate an enum that is set to 'NONE = 0'. No rotation will occur.");
return inEnum;
}
int allValuesMask = values.Max();
if (((int)(object)inEnum ^ allValuesMask) == 0)
{
Console.WriteLine("Rotate<TEnum>() called to rotate an enum that is set to 'ALL', 'OMNI', or a similar status including all flags. No rotation will occur.");
return inEnum;
}
// Remove 0/NONE and OMNI/ALL
values.Remove(0);
values.Remove(values.Max());
if (positions % values.Count == 0)
{
Console.WriteLine("Rotate<ENum>() called with perfect roundness positions % possiblePositions == 0. No rotation will occur.");
return inEnum;
}
int maxRotation = Convert.ToString(values.Max(), 2).Length;
while (Math.Abs(positions) > maxRotation)
{
if (positions > 0)
positions -= maxRotation;
else
positions += maxRotation;
}
if (positions > 0)
{
return (TEnum)(object)(((int)(object)inEnum << positions | (int)(object)inEnum >> (maxRotation - positions)) & allValuesMask);
}
else if (positions < 0)
{
positions = Math.Abs(positions);
return (TEnum)(object)(((int)(object)inEnum >> positions | (int)(object)inEnum << (maxRotation - positions)) & allValuesMask);
}
Console.WriteLine("Rotate<TEnum>() Unable to rotate - unknown condition.");
return inEnum;
}
}
public static class EnumExtensions
{
// Extension of TEnum
public static void Rotate<TEnum>(this ref TEnum inEnum, int positions = 1) where TEnum : struct, System.Enum
{
if (positions == 0)
{
Console.WriteLine("Rotate<TEnum>() called to rotate 0 positions. No rotation will occur.");
return;
}
// Currently doesn't support any underlying enum type but int
if (Enum.GetUnderlyingType(inEnum.GetType()) != typeof(int))
throw new NotSupportedException("This method, Rotate<TEnum>(), is only valud on integer underlying types. The System,Enum '" + inEnum.GetType().Name + "' does not use such an underlying value.");
if (!typeof(TEnum).GetCustomAttributes<FlagsAttribute>().Any())
throw new NotSupportedException("This method, Rotate<TEnum>(), is only valid for enums that have the [System.Flags] attribute set. The System.Enum '" + inEnum.GetType().Name + "' does not use that attribute.");
List<int> values = new List<int>((int[])Enum.GetValues(inEnum.GetType()));
if (values.Any(a => a < 0))
throw new NotSupportedException("This method, Rotate<TEnum>(), is only valid for enums that contain no negative integer values. The System.Enum '" + inEnum.GetType().Name + "' includes negative values.");
if (((int)(object)inEnum ^ 0) == 0)
{
Console.WriteLine("Rotate<TEnum>() called to rotate an enum that is set to 'NONE = 0'. No rotation will occur.");
return;
}
int allValuesMask = values.Max();
if (((int)(object)inEnum ^ allValuesMask) == 0)
{
Console.WriteLine("Rotate<TEnum>() called to rotate an enum that is set to 'ALL', 'OMNI', or a similar status including all flags. No rotation will occur.");
return;
}
// Remove 0/NONE and OMNI/ALL
values.Remove(0);
values.Remove(values.Max());
if (positions % values.Count == 0)
{
Console.WriteLine("Rotate<ENum>() called with perfect roundness positions % possiblePositions == 0. No rotation will occur.");
return;
}
int maxRotation = Convert.ToString(values.Max(), 2).Length;
while (Math.Abs(positions) > maxRotation)
{
if (positions > 0)
positions -= maxRotation;
else
positions += maxRotation;
}
if (positions > 0)
{
inEnum = (TEnum)(object)(((int)(object)inEnum << positions | (int)(object)inEnum >> (maxRotation - positions)) & allValuesMask);
return;
}
else if (positions < 0)
{
positions = Math.Abs(positions);
inEnum = (TEnum)(object)(((int)(object)inEnum >> positions | (int)(object)inEnum << (maxRotation - positions)) & allValuesMask);
return;
}
Console.WriteLine("Rotate<TEnum>() Unable to rotate - unknown condition.");
return;
}
}
[System.Flags]
public enum CompassDirections
{
NONE = 0,
NORTH = 1 << 0,
NORTHEAST = 1 << 1,
EAST = 1 << 2,
SOUTHEAST = 1 << 3,
SOUTH = 1 << 4,
SOUTHWEST = 1 << 5,
WEST = 1 << 6,
NORTHWEST = 1 << 7,
OMNI = ~(~0 << 8)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment