96

I have a very large set of permissions in my application that I represent with a Flags enumeration. It is quickly approaching the practical upper bound of the long data type. And I am forced to come up with a strategy to transition to a different structure soon. Now, I could break this list down into smaller pieces, however, this is already just a subset of the overall permissions for our application, based on our applications layout. We use this distinction extensively for display purposes when managing permissions and I would rather not have to revisit that code at this time if I can avoid it.

Has anybody else run into this issue? How did you get past it? General examples are fine, but I am most interested in a c# specific example if there are any language specific tricks that I can employ to get the job done.

May not be neccessary, but here is the list of Permissions currently defined for the portion of the app I am dealing with.

//Subgroup WebAgent
[Flags]
public enum WebAgentPermission : long
{
    [DescriptionAttribute("View Rule Group")]
    ViewRuleGroup = 1,
    [DescriptionAttribute("Add Rule Group")]
    AddRuleGroup = 2,
    [DescriptionAttribute("Edit Rule Group")]
    EditRuleGroup = 4,
    [DescriptionAttribute("Delete Rule Group")]
    DeleteRuleGroup = 8,
    [DescriptionAttribute("View Rule")]
    ViewRule = 16,
    [DescriptionAttribute("Add Rule")]
    AddRule = 32,
    [DescriptionAttribute("Edit Rule")]
    EditRule = 64,
    [DescriptionAttribute("Delete Rule")]
    DeleteRule = 128,
    [DescriptionAttribute("View Location")]
    ViewLocation = 256,
    [DescriptionAttribute("Add Location")]
    AddLocation = 512,
    [DescriptionAttribute("Edit Location")]
    EditLocation = 1024,
    [DescriptionAttribute("Delete Location")]
    DeleteLocation = 2048,
    [DescriptionAttribute("View Volume Statistics")]
    ViewVolumeStatistics = 4096,
    [DescriptionAttribute("Edit Volume Statistics")]
    EditVolumeStatistics = 8192,
    [DescriptionAttribute("Upload Volume Statistics")]
    UploadVolumeStatistics = 16384,
    [DescriptionAttribute("View Role")]
    ViewRole = 32768,
    [DescriptionAttribute("Add Role")]
    AddRole = 65536,
    [DescriptionAttribute("Edit Role")]
    EditRole = 131072,
    [DescriptionAttribute("Delete Role")]
    DeleteRole = 262144,
    [DescriptionAttribute("View User")]
    ViewUser = 524288,
    [DescriptionAttribute("Add User")]
    AddUser = 1048576,
    [DescriptionAttribute("Edit User")]
    EditUser = 2097152,
    [DescriptionAttribute("Delete User")]
    DeleteUser = 4194304,
    [DescriptionAttribute("Assign Permissions To User")]
    AssignPermissionsToUser = 8388608,
    [DescriptionAttribute("Change User Password")]
    ChangeUserPassword = 16777216,
    [DescriptionAttribute("View Audit Logs")]
    ViewAuditLogs = 33554432,
    [DescriptionAttribute("View Team")]
    ViewTeam = 67108864,
    [DescriptionAttribute("Add Team")]
    AddTeam = 134217728,
    [DescriptionAttribute("Edit Team")]
    EditTeam = 268435456,
    [DescriptionAttribute("Delete Team")]
    DeleteTeam = 536870912,
    [DescriptionAttribute("View Web Agent Reports")]
    ViewWebAgentReports = 1073741824,
    [DescriptionAttribute("View All Locations")]
    ViewAllLocations = 2147483648,
    [DescriptionAttribute("Access to My Search")]
    AccessToMySearch = 4294967296,
    [DescriptionAttribute("Access to Pespective Search")]
    AccessToPespectiveSearch = 8589934592,
    [DescriptionAttribute("Add Pespective Search")]
    AddPespectiveSearch = 17179869184,
    [DescriptionAttribute("Edit Pespective Search")]
    EditPespectiveSearch = 34359738368,
    [DescriptionAttribute("Delete Pespective Search")]
    DeletePespectiveSearch = 68719476736,
    [DescriptionAttribute("Access to Search")]
    AccessToSearch = 137438953472,
    [DescriptionAttribute("View Form Roles")]
    ViewFormRole = 274877906944,
    [DescriptionAttribute("Add / Edit Form Roles")]
    AddFormRole = 549755813888,
    [DescriptionAttribute("Delete UserFormRolesDifferenceMasks")]
    DeleteFormRole = 1099511627776,
    [DescriptionAttribute("Export Locations")]
    ExportLocations = 2199023255552,
    [DescriptionAttribute("Import Locations")]
    ImportLocations = 4398046511104,
    [DescriptionAttribute("Manage Location Levels")]
    ManageLocationLevels = 8796093022208,
    [DescriptionAttribute("View Job Title")]
    ViewJobTitle = 17592186044416,
    [DescriptionAttribute("Add Job Title")]
    AddJobTitle = 35184372088832,
    [DescriptionAttribute("Edit Job Title")]
    EditJobTitle = 70368744177664,
    [DescriptionAttribute("Delete Job Title")]
    DeleteJobTitle = 140737488355328,
    [DescriptionAttribute("View Dictionary Manager")]
    ViewDictionaryManager = 281474976710656,
    [DescriptionAttribute("Add Dictionary Manager")]
    AddDictionaryManager = 562949953421312,
    [DescriptionAttribute("Edit Dictionary Manager")]
    EditDictionaryManager = 1125899906842624,
    [DescriptionAttribute("Delete Dictionary Manager")]
    DeleteDictionaryManager = 2251799813685248,
    [DescriptionAttribute("View Choice Manager")]
    ViewChoiceManager = 4503599627370496,
    [DescriptionAttribute("Add Choice Manager")]
    AddChoiceManager = 9007199254740992,
    [DescriptionAttribute("Edit Chioce Manager")]
    EditChoiceManager = 18014398509481984,
    [DescriptionAttribute("Delete Choice Manager")]
    DeleteChoiceManager = 36028797018963968,
    [DescriptionAttribute("Import Export Choices")] //57
    ImportExportChoices = 72057594037927936
}
Matthew Vines
  • 27,253
  • 7
  • 76
  • 97
  • 82
    For clarity I usually use: (1 << 0), (1 << 1), .. (1 << 57) for my flags. Easier to comprehend and harder to get the value wrong. Doesn't answer your question, though. – Talljoe Jun 29 '09 at 21:59
  • thanks, I had tried a few ways to do something similar, but I always got can't use calculated value for enums error. – Matthew Vines Jun 29 '09 at 22:01
  • Really? Can you show an example? It should be legal to use calculated values in an initialization for an enum member, so long as doing so does not cause loops in the dependency chain. – Eric Lippert Jun 30 '09 at 02:30
  • Well, I retract my previous statement. I probably tried something like (long)Math.Pow(2,2) before to test the waters, which fails with 'Expression assigned to ... must be constant.' I'm sure I just failed to test anything else, thanks for calling me on it. I learned something. – Matthew Vines Jun 30 '09 at 14:31
  • I should note that (1 << n) works for 32 flags, but will then repeat as the bit position wraps to the beginning of the word. I used (2 ^ n) instead. – Matthew Vines Jun 30 '09 at 17:01
  • 4
    I think ^ does not mean what you think it means. – Eric Lippert Jul 01 '09 at 01:37
  • 30
    If you want to make a long bitshift, tell the compiler that's what you want. (1L << 40) should work just fine. – Eric Lippert Jul 01 '09 at 01:38
  • 1
    Well, explative, That's what I get for rushing through this, thanks yet again for pointing something silly I did out :) – Matthew Vines Jul 01 '09 at 01:47
  • @Talljoe : Today I learned something, today is a good day! So much easier to read & don't have to use spreadsheet to calculate the numbers to make sure I get them right! – Morvael Aug 22 '18 at 09:55

10 Answers10

47

I see values from at least a handful of different enumerations in there...

My first thought was to approach the problem by splitting the permissions up in logical groups (RuleGroupPermissions, RulePermissions, LocationPermissions, ...), and then having a class (WebAgentPermissions) exposing a property for each permission enum type.

Since the permission values seem repetitive, you could probably get away with a single enum in the end:

[Flags]
public enum Permissions
{
    View = 1,
    Add = 2,
    Edit = 4,
    Delete = 8
}

And then have the WebAgentPermissions class expose a property for each area where permissions are to be set;

class WebAgentPermissions
{
    public Permissions RuleGroup { get; set; }
    public Permissions Rule { get; set; }
    public Permissions Location { get; set; }
    // and so on...
}
mayo
  • 3,845
  • 1
  • 32
  • 42
Fredrik Mörk
  • 155,851
  • 29
  • 291
  • 343
  • 2
    I like this, and while they are pretty standard, I think there is enough deviation to make this solution somewhat cumbersome. In the end I think I will just have to suck it up and break this beast up into smaller pieces, and re engineer a few sections of my application to handle the changes. I was just hoping there was a magic bullet out there I hadn't found. Thanks for your time. – Matthew Vines Jun 29 '09 at 22:27
  • 3
    That's a pretty good solution in this case. Unfortunately it doesn't give a generic way of dealing with similar problems, except 'try to split it to the best of the enum meaning'. – PPC Apr 11 '12 at 17:53
  • 1
    This becomes tricky if your possible operations are not just CRUD, you may need multiple permission enums. I cannot imagine trying to store all of these into a db either, you'd be joining a ton of tables every time you need your user's permissions. – perustaja Dec 28 '20 at 19:13
31

Language documentation says:

http://msdn.microsoft.com/en-us/library/system.flagsattribute.aspx

"The underlying type is Int32 and so the maximum single bit flag is 1073741824 and obviously there are a total of 32 flags for each enum."

However... UPDATED:

Commenter is correct. Check out this:

http://msdn.microsoft.com/en-us/library/ms182147(VS.80).aspx

Int32 is only the DEFAULT datatype! In fact you can specify Int64.

public enum MyEnumType : Int64

...allowing up to 64 values. But that certainly seems to be the maximum, after that you're going to be looking at re-engineering. Without knowing too much about the rest of your solution, I can't say exactly what might suit. But an array (or hash-map) of privilege identifiers is probably the most natural approach.

the.jxc
  • 3,373
  • 21
  • 21
  • the quote you posted is from a commenter, and I believe he is incorrect. The Flag attribute does not specify any type information at all. Though enums are Int32 by default if you do not explicitly specify a type. – Matthew Vines Jun 29 '09 at 22:23
  • The quote referenced in this answer no longer seems to be on the page it was taken from. – Panzercrisis Oct 03 '17 at 18:36
  • 1
    @MatthewVines has specified `public enum WebAgentPermission : long` and that is the same as using `Int64` – Ogglas Apr 20 '18 at 08:13
  • @leigero That screams design smell to me tbh. Maybe look into breaking those values up or changing the structure? For example if it's for permissions, perhaps consider using claims instead, unless there's a particular reason to be using binary for this structure. Even if you must use binary, you can still perform bitwise operations across multiple fields (e.g. `((p.group | p.location) & MY_PERMISSION) == MY_PERMISSION`) ya know? – Sinaesthetic Jul 04 '18 at 04:51
  • 8
    TIP: When using `enum MyEnumType : Int64` with shift operator: `[Flags] public enum MyEnumFlags : Int64 { None = 0, A = 1 << 0, B = 1 << 1, C = 1 << 2, ...etc... ` be sure to use 1**L** `A = 1L << 0, B = 1L << 1,` to tell the compiler that the 1 is a long/Int64. Otherwise it interprets it as int32, and at the 32nd you restart at 1, giving you double values for other enum names. – Erik Nov 03 '19 at 23:40
15

Not an answer to your question, but a related suggestion: we use bitshifting to specify the numeric values, like so:

[Flags]
public enum MyEnumFlags : Int64
{
    None = 0,
    A = 1 << 0,
    B = 1 << 1,
    C = 1 << 2,
    D = 1 << 3,
    E = 1 << 4,
    F = 1 << 5,
    ...etc...

Not so important for the first ten, but after that it gets really handy.

DavidMoksha
  • 163
  • 1
  • 5
14

This turned out to be a more common problem than I thought it would be, where I was representing CSS classes as flags types and there were more than 64 possibilities. I've taken all I learned from that process and turned it into a reusable pattern, albeit since it's a struct, it's a copy-and-paste type pattern.

This is the BigFlags "enumerated type". It uses either BigInteger from System.Numerics, or if there is no way you can reference that assembly, there is a fallback that uses BitArray by simply turning off the NUMERICS preprocessor directive.

It behaves remarkably like a Flags enum, even defining such things as HasFlag(...), GetNames(), GetValues(), TryParse(...), a TypeConverter, IConvertible, etc. Since it does define a TypeConverter and IConvertible, it's also suitable for storing in a data store, albeit always as a string or text data type.

You expose the "enum" values as public static readonly members. Combined enum values are exposed as get-only properties.

To use it, copy and paste the code, then do a search and replace on BigFlags with your struct name, then delete the enums in the TODO section and add your values.

Hope somebody finds it useful.

#define NUMERICS

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
#if NUMERICS
using System.Numerics;
#endif
using System.Reflection;
using System.Text;
using System.Threading.Tasks;


namespace Aim
{
    /// <summary>
    /// The BigFlags struct behaves like a Flags enumerated type.
    /// <para>
    /// Note that if this struct will be stored in some type of data
    /// store, it should be stored as a string type. There are two
    /// reasons for this:
    /// </para>
    /// <para>
    /// 1. Presumably, this pattern is being used because the number
    /// of values will exceed 64 (max positions in a long flags enum).
    /// Since this is so, there is in any case no numeric type which
    /// can store all the possible combinations of flags.
    /// </para>
    /// <para>
    /// 2. The "enum" values are assigned based on the order that the
    /// static public fields are defined. It is much safer to store
    /// these fields by name in case the fields are rearranged. This
    /// is particularly important if this represents a permission set!
    /// </para>
    /// </summary>
    [
    TypeConverter( typeof( BigFlagsConverter ) )
    ]
    public struct BigFlags : IEquatable<BigFlags>,
        IComparable<BigFlags>, IComparable, IConvertible
    {
        #region State...

        private static readonly List<FieldInfo> Fields;
        private static readonly List<BigFlags> FieldValues;
#if NUMERICS
        private static readonly bool ZeroInit = true;
        private BigInteger Value;

        /// <summary>
        /// Creates a value taking ZeroInit into consideration.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        private static BigInteger CreateValue( int index )
        {
            if( ZeroInit && index == 0 )
            {
                return 0;
            }
            int idx = ZeroInit ? index - 1 : index;

            return new BigInteger( 1 ) << idx;
        }
#else
        private BitArray Array;

        /// <summary>
        /// Lazy-initialized BitArray.
        /// </summary>
        private BitArray Bits
        {
            get
            {
                if( null == Array )
                {
                    Array = new BitArray( Fields.Count );
                }
                return Array;
            }
        }
#endif
        #endregion ...State

        #region Construction...

        /// <summary>
        /// Static constructor. Sets the static public fields.
        /// </summary>
        static BigFlags()
        {
            Fields = typeof( BigFlags ).GetFields(
                BindingFlags.Public | BindingFlags.Static ).ToList();
            FieldValues = new List<BigFlags>();
            for( int i = 0; i < Fields.Count; i++ )
            {
                var field = Fields[i];
                var fieldVal = new BigFlags();
#if NUMERICS
                fieldVal.Value = CreateValue( i );
#else
                fieldVal.Bits.Set( i, true );
#endif
                field.SetValue( null, fieldVal );
                FieldValues.Add( fieldVal );
            }
        }
        #endregion ...Construction

        #region Operators...

        /// <summary>
        /// OR operator. Or together BigFlags instances.
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static BigFlags operator |( BigFlags lhs, BigFlags rhs )
        {
            var result = new BigFlags();
#if NUMERICS
            result.Value = lhs.Value | rhs.Value;
#else
            // BitArray is modified in place - always copy!
            result.Array = new BitArray( lhs.Bits ).Or( rhs.Bits );
#endif

            return result;
        }

        /// <summary>
        /// AND operator. And together BigFlags instances.
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static BigFlags operator &( BigFlags lhs, BigFlags rhs )
        {
            var result = new BigFlags();
#if NUMERICS
            result.Value = lhs.Value & rhs.Value;
#else
            // BitArray is modified in place - always copy!
            result.Array = new BitArray( lhs.Bits ).And( rhs.Bits );
#endif

            return result;
        }

        /// <summary>
        /// XOR operator. Xor together BigFlags instances.
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static BigFlags operator ^( BigFlags lhs, BigFlags rhs )
        {
            var result = new BigFlags();
#if NUMERICS
            result.Value = lhs.Value ^ rhs.Value;
#else
            // BitArray is modified in place - always copy!
            result.Array = new BitArray( lhs.Bits ).Xor( rhs.Bits );
#endif

            return result;
        }

        /// <summary>
        /// Equality operator.
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static bool operator ==( BigFlags lhs, BigFlags rhs )
        {
            return lhs.Equals( rhs );
        }

        /// <summary>
        /// Inequality operator.
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static bool operator !=( BigFlags lhs, BigFlags rhs )
        {
            return !( lhs == rhs );
        }
        #endregion ...Operators

        #region System.Object Overrides...

        /// <summary>
        /// Overridden. Returns a comma-separated string.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
#if NUMERICS
            if( ZeroInit && Value == 0 )
            {
                return Fields[0].Name;
            }
#endif
            var names = new List<string>();
            for( int i = 0; i < Fields.Count; i++ )
            {
#if NUMERICS
                if( ZeroInit && i == 0 )
                    continue;

                var bi = CreateValue( i );
                if( ( Value & bi ) ==  bi )
                    names.Add( Fields[i].Name );
#else
                if( Bits[i] )
                    names.Add( Fields[i].Name );
#endif
            }

            return String.Join( ", ", names );
        }

        /// <summary>
        /// Overridden. Compares equality with another object.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals( object obj )
        {
            if( obj is BigFlags )
            {
                return Equals( (BigFlags)obj );
            }

            return false;
        }

        /// <summary>
        /// Overridden. Gets the hash code of the internal BitArray.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
#if NUMERICS
            return Value.GetHashCode();
#else
            int hash = 17;
            for( int i = 0; i < Bits.Length; i++ )
            {
                if( Bits[i] )
                    hash ^= i;
            }

            return hash;
#endif
        }
        #endregion ...System.Object Overrides

        #region IEquatable<BigFlags> Members...

        /// <summary>
        /// Strongly-typed equality method.
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool Equals( BigFlags other )
        {
#if NUMERICS
            return Value == other.Value;
#else
            for( int i = 0; i < Bits.Length; i++ )
            {
                if( Bits[i] != other.Bits[i] )
                    return false;
            }

            return true;
#endif
        }
        #endregion ...IEquatable<BigFlags> Members

        #region IComparable<BigFlags> Members...

        /// <summary>
        /// Compares based on highest bit set. Instance with higher
        /// bit set is bigger.
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public int CompareTo( BigFlags other )
        {
#if NUMERICS
            return Value.CompareTo( other.Value );
#else
            for( int i = Bits.Length - 1; i >= 0; i-- )
            {
                bool thisVal = Bits[i];
                bool otherVal = other.Bits[i];
                if( thisVal && !otherVal )
                    return 1;
                else if( !thisVal && otherVal )
                    return -1;
            }

            return 0;
#endif
        }
        #endregion ...IComparable<BigFlags> Members

        #region IComparable Members...

        int IComparable.CompareTo( object obj )
        {
            if( obj is BigFlags )
            {
                return CompareTo( (BigFlags)obj );
            }

            return -1;
        }
        #endregion ...IComparable Members

        #region IConvertible Members...

        /// <summary>
        /// Returns TypeCode.Object.
        /// </summary>
        /// <returns></returns>
        public TypeCode GetTypeCode()
        {
            return TypeCode.Object;
        }

        bool IConvertible.ToBoolean( IFormatProvider provider )
        {
            throw new NotSupportedException();
        }

        byte IConvertible.ToByte( IFormatProvider provider )
        {
#if NUMERICS
            return Convert.ToByte( Value );
#else
            throw new NotSupportedException();
#endif
        }

        char IConvertible.ToChar( IFormatProvider provider )
        {
            throw new NotSupportedException();
        }

        DateTime IConvertible.ToDateTime( IFormatProvider provider )
        {
            throw new NotSupportedException();
        }

        decimal IConvertible.ToDecimal( IFormatProvider provider )
        {
#if NUMERICS
            return Convert.ToDecimal( Value );
#else
            throw new NotSupportedException();
#endif
        }

        double IConvertible.ToDouble( IFormatProvider provider )
        {
#if NUMERICS
            return Convert.ToDouble( Value );
#else
            throw new NotSupportedException();
#endif
        }

        short IConvertible.ToInt16( IFormatProvider provider )
        {
#if NUMERICS
            return Convert.ToInt16( Value );
#else
            throw new NotSupportedException();
#endif
        }

        int IConvertible.ToInt32( IFormatProvider provider )
        {
#if NUMERICS
            return Convert.ToInt32( Value );
#else
            throw new NotSupportedException();
#endif
        }

        long IConvertible.ToInt64( IFormatProvider provider )
        {
#if NUMERICS
            return Convert.ToInt64( Value );
#else
            throw new NotSupportedException();
#endif
        }

        sbyte IConvertible.ToSByte( IFormatProvider provider )
        {
#if NUMERICS
            return Convert.ToSByte( Value );
#else
            throw new NotSupportedException();
#endif
        }

        float IConvertible.ToSingle( IFormatProvider provider )
        {
#if NUMERICS
            return Convert.ToSingle( Value );
#else
            throw new NotSupportedException();
#endif
        }

        string IConvertible.ToString( IFormatProvider provider )
        {
            return ToString();
        }

        object IConvertible.ToType( Type conversionType, IFormatProvider provider )
        {
            var tc = TypeDescriptor.GetConverter( this );

            return tc.ConvertTo( this, conversionType );
        }

        ushort IConvertible.ToUInt16( IFormatProvider provider )
        {
#if NUMERICS
            return Convert.ToUInt16( Value );
#else
            throw new NotSupportedException();
#endif
        }

        uint IConvertible.ToUInt32( IFormatProvider provider )
        {
#if NUMERICS
            return Convert.ToUInt32( Value );
#else
            throw new NotSupportedException();
#endif
        }

        ulong IConvertible.ToUInt64( IFormatProvider provider )
        {
#if NUMERICS
            return Convert.ToUInt64( Value );
#else
            throw new NotSupportedException();
#endif
        }
        #endregion ...IConvertible Members

        #region Public Interface...

        /// <summary>
        /// Checks <paramref name="flags"/> to see if all the bits set in
        /// that flags are also set in this flags.
        /// </summary>
        /// <param name="flags"></param>
        /// <returns></returns>
        public bool HasFlag( BigFlags flags )
        {
            return ( this & flags ) == flags;
        }

        /// <summary>
        /// Gets the names of this BigFlags enumerated type.
        /// </summary>
        /// <returns></returns>
        public static string[] GetNames()
        {
            return Fields.Select( x => x.Name ).ToArray();
        }

        /// <summary>
        /// Gets all the values of this BigFlags enumerated type.
        /// </summary>
        /// <returns></returns>
        public static BigFlags[] GetValues()
        {
            return FieldValues.ToArray();
        }

        /// <summary>
        /// Standard TryParse pattern. Parses a BigFlags result from a string.
        /// </summary>
        /// <param name="s"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public static bool TryParse( string s, out BigFlags result )
        {
            result = new BigFlags();
            if( String.IsNullOrEmpty( s ) )
                return true;

            var fieldNames = s.Split( ',' );
            foreach( var f in fieldNames )
            {
                var field = Fields.FirstOrDefault( x =>
                    String.Equals( x.Name, f.Trim(),
                    StringComparison.OrdinalIgnoreCase ) );
                if( null == field )
                {
                    result = new BigFlags();
                    return false;
                }
#if NUMERICS
                int i = Fields.IndexOf( field );
                result.Value |= CreateValue( i );
#else
                result.Bits.Set( Fields.IndexOf( field ), true );
#endif
            }

            return true;
        }

        //
        // Expose "enums" as public static readonly fields.
        // TODO: Replace this section with your "enum" values.
        //
        public static readonly BigFlags None;
        public static readonly BigFlags FirstValue;
        public static readonly BigFlags ValueTwo;
        public static readonly BigFlags ValueThree;
        public static readonly BigFlags ValueFour;
        public static readonly BigFlags ValueFive;
        public static readonly BigFlags ValueSix;
        public static readonly BigFlags LastValue;

        /// <summary>
        /// Expose flagged combinations as get-only properties.
        /// </summary>
        public static BigFlags FirstLast
        {
            get
            {
                return BigFlags.FirstValue | BigFlags.LastValue;
            }
        }
        #endregion ...Public Interface
    }

    /// <summary>
    /// Converts objects to and from BigFlags instances.
    /// </summary>
    public class BigFlagsConverter : TypeConverter
    {
        /// <summary>
        /// Can convert to string only.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="destinationType"></param>
        /// <returns></returns>
        public override bool CanConvertTo( ITypeDescriptorContext context,
            Type destinationType )
        {
            return destinationType == typeof( String );
        }

        /// <summary>
        /// Can convert from any object.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="sourceType"></param>
        /// <returns></returns>
        public override bool CanConvertFrom( ITypeDescriptorContext context,
            Type sourceType )
        {
            return true;
        }

        /// <summary>
        /// Converts BigFlags to a string.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="culture"></param>
        /// <param name="value"></param>
        /// <param name="destinationType"></param>
        /// <returns></returns>
        public override object ConvertTo( ITypeDescriptorContext context,
            CultureInfo culture, object value, Type destinationType )
        {
            if( value is BigFlags && CanConvertTo( destinationType ) )
                return value.ToString();

            return null;
        }

        /// <summary>
        /// Attempts to parse <paramref name="value"/> and create and
        /// return a new BigFlags instance.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="culture"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override object ConvertFrom( ITypeDescriptorContext context,
            CultureInfo culture, object value )
        {
            var s = Convert.ToString( value );
            BigFlags result;
            BigFlags.TryParse( s, out result );

            return result;
        }
    }
}
Bruce Pierson
  • 567
  • 7
  • 11
13

You can check BitArray class. Maybe you will use it in future.

arbiter
  • 9,447
  • 1
  • 32
  • 43
  • This is a good option, but of course you should encapsulate it in a class that gives names to all those bits as some of the other posters suggested. – Doug McClean Jun 29 '09 at 23:13
  • The BitArray class is ideal - define a class with a public enumeration for the permisions (just successive integers starting at 0), and have a query function that just returns the values of the private BitArray member indexed by the permission enumeration). This will handle an arbitrary number of permissions efficiently. – Stephen C. Steel Jun 29 '09 at 23:36
  • Nice. And it's supported back in 1.0. – HelloSam Nov 21 '13 at 08:16
  • 1
    @arbiter. This is an interesting solution. What if I want to check for the presence of a bit when the value is stored in a DB? – Paul Fleming Mar 14 '14 at 14:27
5

In C#, one flexible way to represent a value that is sort of an enumeration but more flexible is to represent it as a static class with precooked values available, like this:

public sealed class WebAgentPermission
{
    private long ID;

    public static readonly WebAgentPermission
        ViewRuleGroup = new WebAgentPermission { ID = 1 };
    public static readonly WebAgentPermission
        AddRuleGroup  = new WebAgentPermission { ID = 2 };

    private WebAgentPermission() { } 

    // considerations: override equals/gethashcode, probably override tostring,
    // maybe implicit cast to/from long, maybe other stuff
}

Alternatively, just split the thing up; it looks like you could, if you really tried.

mqp
  • 70,359
  • 14
  • 95
  • 123
4

If I were in control of this application, I would probably come up with a common set of permissions (View, Add, Edit, Delete, Upload/Import) and a set of resources (Users, Roles, Rules, etc). On the web page find the resource type associated with that page and then check the permissions. Perhaps something like:

Permissions perms = agent.GetPermissions(ResourceType.User);
if((perms & Permissions.View) == Permissions.View) { /* do work */ }

or

Permissions perms = agent.Permissions[ResourceType.User];
if((perms & Permissions.View) == Permissions.View) { /* do work */ }

or even

if(agent.IsAuthorized(ResourceType.User, Permissions.View)) { /* do work */ }

You have a couple of permissions that don't make sense with everything else (Assign Permissoins to user, to name one). I'm not sure how I would handle that based on how little I know the problem.

Talljoe
  • 14,593
  • 4
  • 43
  • 39
2

I have not been in this situation.

Here is what I think, create separate enums for each of the category & accept those as parameters.

RuleGroupPermission
    None = 0
    ViewRuleGroup = 1,
    AddRuleGroup = 2,
    EditRuleGroup = 4,
    DeleteRuleGroup = 8,

LocationOperations
    None = 0
    Add = 1
    View = 2
    Delete = 4

void setPermission(RuleGroupPermission ruleGroupOpsAllowed, LocationOperations locationOptions)
{
   ...
}

EDIT: Look at how messagebox.show does it. OK, OKCancel separated from Question, Information, Exclamation.

shahkalpesh
  • 33,172
  • 3
  • 63
  • 88
1

For others facing a similar problem, I suggest InfiniteEnumFlags library, it has all of the dotnet enum flags features and more.

AliReza Sabouri
  • 4,355
  • 2
  • 25
  • 37
0

How about... having the permissions in a list?

They can be loaded from a database when the program starts, and modified without redeploying the system!

public class Permission
{
    public string Description;
    public string Code;
    public bool IsSet;
}

List<Permission> permissions;
Graham Laight
  • 4,700
  • 3
  • 29
  • 28