Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Please add HasAnyFlag(Enum flags) to Enum #9802

Closed
RioMcBoo opened this issue Mar 17, 2025 · 3 comments
Closed

Please add HasAnyFlag(Enum flags) to Enum #9802

RioMcBoo opened this issue Mar 17, 2025 · 3 comments

Comments

@RioMcBoo
Copy link

RioMcBoo commented Mar 17, 2025

Currently, there is already a HasFlag method, but it does not allow us to use enum as flags to its full potential. And we have to invent a new wheel.

All that is needed is for the Enum class to have two methods:

bool HasFlag(Enum mask) => this.mask & mask == mask;
bool HasAnyFlag(Enum mask) => this.mask & mask != 0;
@Youssef1313
Copy link
Member

This was previously proposed in dotnet/runtime#69262.

@RioMcBoo
Copy link
Author

RioMcBoo commented Mar 18, 2025

Judging by the policy, which implies support for old code, there will never be changes in this direction. Because this type is unique and will always be used in the code.

I don't understand at all why this type is some kind of crutch in dotnet when there are many examples of using metacoding.

For example, the "Record" type, which hide the implementation of classes under the hood in automatic mode.

Why can't we do something similar with enums, so that it would be a full-fledged type that implements the IBinaryInteger interface and can be used in various generics?

I gave an example of this type below:

using System.Numerics;
using System.Reflection;
using System.Text;

namespace CustomEnum
{
    public static class FlagsExtensions
    {
        public static bool HasAnyFlag<T>(this T value, T flag) where T : IBinaryInteger<T>
        {
            return (value & flag) != T.Zero;
        }

        public static bool HasFlag<T>(this T value, T flag) where T : IBinaryInteger<T>
        {
            return (value & flag) == flag;
        }
    }

    [AttributeUsage(AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
    public class FlagsAttribute : Attribute
    {
        public FlagsAttribute() { }
        public FlagsAttribute(string separator)
        {
            FlagSeparator = separator;
        }

        public string FlagSeparator;
    }

    [AttributeUsage(AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
    public class EnumOptionsAttribute : Attribute
    {
        public EnumOptionsAttribute(bool includeMyNameInString /*you can add other*/)
        {
            IncludeMyNameInString = includeMyNameInString;
        }

        public bool IncludeMyNameInString;
    }

    public interface IEnum<T> where T : unmanaged, IBinaryInteger<T> {}

    [Flags(" * ")]
    [EnumOptions(includeMyNameInString: true)]
    public struct Fruits : IEnum</*generic*/int> /*,you can do automatically more from IBinaryInteger<Fruits>*/
    {
        public static readonly Fruits None = new(0);
        public static readonly Fruits Apple = new(1);
        public static readonly Fruits Banana = new(2);
        public static readonly Fruits Orange = new(4);
        public static readonly Fruits Mango = new(8);
        public static readonly Fruits Pineapple = new(16);
        public static readonly Fruits Strawberry = new(32);
        public static readonly Fruits Watermelon = new(64);

        #region HiddenPart
        private /*generic*/int _value;
        public bool HasFlag(/*generic*/Fruits right) => _value.HasFlag(right._value);
        public bool HasAnyFlag(/*generic*/Fruits right) => _value.HasAnyFlag(right._value);
        private static readonly int BitSize = sizeof(/*generic*/int) * 8;
        private static readonly Dictionary</*generic*/int, string> Names;
        private static readonly bool AmIFlagsList;
        private static readonly bool IncludeMyNameInString;
        private static readonly string FlagSeparator = " | ";
        private static readonly string EnumName = /*generic*/"Fruits";
        private static readonly string UnknownFormat = "X" + (BitSize / 4).ToString();

        private bool FlagToString(out string stringValue)
        {
            var builder = new StringBuilder();
            bool hasRecognizedName = false;
            /*generic*/int remains = _value;

            for (int i = 0; i < BitSize && remains != 0; i++)
            {
                var flag = (/*generic*/int)1 << i;
                if (remains.HasFlag(flag))
                {
                    if (builder.Length != 0)
                        builder.Append(FlagSeparator);

                    if (Names.TryGetValue(flag, out var name))
                    {
                        if (IncludeMyNameInString)
                            builder.Append(EnumName + ".");

                        builder.Append(name);
                        hasRecognizedName = true;
                    }
                    else
                    {
                        builder.Append(flag.ToString(UnknownFormat));
                    }
                }

                remains &= ~flag;
            }

            stringValue = builder.ToString();
            return hasRecognizedName;
        }

        private bool ValueToString(out string stringValue)
        {
            if (!Names.TryGetValue(_value, out stringValue))
            {
                stringValue = _value.ToString();
                return false;
            }

            if (IncludeMyNameInString)
            stringValue = EnumName + "." + stringValue;

            return true;
        }  

        public override string ToString()
        {
            string stringValue = string.Empty;

            if (ValueToString(out stringValue))
                return stringValue;

            if (IsFlag && FlagToString(out stringValue))
                return stringValue;

            return _value.ToString();
        }

        public /*generic*/Fruits(/*generic*/int value)
        {
            _value = value;
        }

        static /*generic*/Fruits()
        {
            var EnumInfo = typeof(/*generic*/Fruits);
            if (EnumInfo.GetCustomAttribute<FlagsAttribute>() is FlagsAttribute flagOptions)
            {
                AmIFlagsList = true;
                if (flagOptions.FlagSeparator != null)
                    FlagSeparator = flagOptions.FlagSeparator;
            }

            if (EnumInfo.GetCustomAttribute<EnumOptionsAttribute>() is EnumOptionsAttribute enumOptions)
            {
                IncludeMyNameInString = enumOptions.IncludeMyNameInString;
            }

            Names = new();
            foreach (var item in EnumInfo.GetFields(BindingFlags.Public | BindingFlags.Static))
            {
                var value = (/*generic*/Fruits)item.GetValue(null);
                
                Names.Add(value._value, item.Name);
            }
        }

        #region Operators /*,you can do automatically more from IBinaryInteger<Fruits>*/
        public static /*generic*/Fruits operator |(/*generic*/Fruits left, /*generic*/Fruits right) => new(left._value | right._value);

        public static /*generic*/Fruits operator &(/*generic*/Fruits left, /*generic*/Fruits right) => new(left._value & right._value);

        public static /*generic*/Fruits operator ^(/*generic*/Fruits left, /*generic*/Fruits right) => new(left._value ^ right._value);

        public static /*generic*/Fruits operator ~(/*generic*/Fruits value) => new(~value._value);

        public static bool operator >(/*generic*/Fruits left, /*generic*/Fruits right) => left._value > right._value;

        public static bool operator >=(/*generic*/Fruits left, /*generic*/Fruits right) => left._value >= right._value;

        public static bool operator <(/*generic*/Fruits left, /*generic*/Fruits right) => left._value < right._value;

        public static bool operator <=(/*generic*/Fruits left, /*generic*/Fruits right) => left._value <= right._value;

        public static /*generic*/Fruits operator %(/*generic*/Fruits left, /*generic*/Fruits right) => new(left._value % right._value);

        public static /*generic*/Fruits operator +(/*generic*/Fruits left, /*generic*/Fruits right) => new(left._value + right._value);

        public static /*generic*/Fruits operator --(/*generic*/Fruits value) => new(value._value - 1);

        public static /*generic*/Fruits operator ++(/*generic*/Fruits value) => new(value._value + 1);

        public static /*generic*/Fruits operator /(/*generic*/Fruits left, /*generic*/Fruits right) => new(left._value / right._value);

        public static bool operator ==(/*generic*/Fruits left, /*generic*/Fruits right) => left.Equals(right);

        public static bool operator !=(/*generic*/Fruits left, /*generic*/Fruits right) => !left.Equals(right);

        public static /*generic*/Fruits operator *(/*generic*/Fruits left, /*generic*/Fruits right) => new(left._value * right._value);

        public static /*generic*/Fruits operator -(/*generic*/Fruits left, /*generic*/Fruits right) => new(left._value - right._value);

        public static /*generic*/Fruits operator -(/*generic*/Fruits value) => new(-value._value);

        public static /*generic*/Fruits operator +(/*generic*/Fruits value) => new(+value._value);

        public static /*generic*/Fruits operator <<(/*generic*/Fruits value, int shiftAmount) => new(value._value << shiftAmount);

        public static /*generic*/Fruits operator >>(/*generic*/Fruits value, int shiftAmount) => new(value._value >> shiftAmount);

        public static /*generic*/Fruits operator >>>(/*generic*/Fruits value, int shiftAmount) => new(value._value >>> shiftAmount);

        public bool Equals(Fruits other) => _value == other._value;

        public override bool Equals(object obj) => obj is /*generic*/Fruits other && Equals(other);

        public override int GetHashCode() => _value.GetHashCode();
        #endregion
        public /*generic*/int Value => _value;
        public string StringValue => ToString();
        public bool IsFlag => AmIFlagsList;
        #endregion
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            Fruits fruits = Fruits.Apple | Fruits.Banana | Fruits.Orange;
            Fruits fruits1 = Fruits.Watermelon;
            if (fruits1 != fruits)
                Console.WriteLine(fruits);
        }
    }
}

@jozkee
Copy link
Member

jozkee commented Mar 21, 2025

@RioMcBoo if your proposal overcomes the pit of failures described in dotnet/runtime#69262, please file a new issue in that repo.

@jozkee jozkee closed this as not planned Won't fix, can't repro, duplicate, stale Mar 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants