c# permissions enums types bitmask

c# - Qué hacer cuando la enumeración de la máscara de bits(flags) es demasiado grande



permissions enums (7)

En C #, una forma flexible de representar un valor que es una especie de enumeración pero más flexible es representarlo como una clase estática con valores precocinados disponibles, como este:

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 }

Alternativamente, simplemente dividir la cosa; Parece que podrías, si realmente lo intentaste.

Tengo un conjunto muy grande de permisos en mi aplicación que represento con una enumeración de Flags. Se está acercando rápidamente al límite superior práctico del tipo de datos largos. Y me veo obligado a idear una estrategia para la transición a una estructura diferente pronto. Ahora, podría dividir esta lista en partes más pequeñas, sin embargo, esto ya es solo un subconjunto de los permisos generales para nuestra aplicación, basado en el diseño de nuestras aplicaciones. Utilizamos esta distinción ampliamente para fines de visualización al administrar permisos y preferiría no tener que volver a visitar ese código en este momento si puedo evitarlo.

¿Alguien más se ha topado con este problema? ¿Cómo lo superaste? Los ejemplos generales están bien, pero estoy más interesado en el ejemplo específico de ac # si hay algún truco específico de idioma que pueda emplear para hacer el trabajo.

Puede que no sea necesario, pero aquí está la lista de permisos actualmente definidos para la parte de la aplicación que estoy tratando.

//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 }


Esto resultó ser un problema más común de lo que pensé, ya que representaba clases de CSS como tipos de banderas y había más de 64 posibilidades. Tomé todo lo que aprendí de ese proceso y lo convertí en un patrón reutilizable, aunque como es una estructura, es un patrón de tipo copiar y pegar.

Este es el "tipo enumerado" de BigFlags . Utiliza BigInteger de System.Numerics , o si no hay forma de que puedas hacer referencia a ese ensamblaje, existe una alternativa que usa BitArray simplemente desactivando la directiva del preprocesador NUMERICS .

Se comporta notablemente como una enumeración de Flags , incluso definiendo cosas como HasFlag(...) , GetNames() , GetValues() , TryParse(...) , un TypeConverter , IConvertible , etc. Dado que define un TypeConverter e IConvertible , también es adecuado para almacenar en un almacén de datos, aunque siempre como una cadena o tipo de datos de texto.

Expones los valores de "enumeración" como miembros public static readonly . Los valores de enumeración combinados se exponen como propiedades de solo obtención.

Para usarlo, copie y pegue el código, luego haga una búsqueda y reemplace en BigFlags con el nombre de su estructura, luego elimine las enumeraciones en la sección TODO y agregue sus valores.

Espero que alguien lo encuentre útil.

#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; } } }


La documentación del idioma dice:

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

"El tipo subyacente es Int32 y, por lo tanto, el indicador de un solo bit máximo es 1073741824 y, obviamente, hay un total de 32 indicadores para cada enumeración".

Sin embargo ... ACTUALIZADO:

El comentarista es correcto. Mira esto:

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

Int32 es solo el tipo de datos por defecto! De hecho puedes especificar Int64.

public enum MyEnumType : Int64

... permitiendo hasta 64 valores. Pero eso ciertamente parece ser el máximo, después de eso vas a estar buscando reingeniería. Sin saber demasiado sobre el resto de su solución, no puedo decir exactamente lo que podría ser adecuado. Pero una matriz (o hash-map) de identificadores de privilegios es probablemente el enfoque más natural.


No he estado en esta situación.

Esto es lo que pienso, crear enumeraciones separadas para cada categoría y aceptarlas como parámetros.

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) { ... }

EDITAR: Mira cómo messagebox.show lo hace. OK, OKCancelar separado de Pregunta, Información, Exclamación.


Puedes consultar la clase BitArray . Tal vez lo uses en el futuro.


Si tuviera el control de esta aplicación, probablemente obtendría un conjunto común de permisos (Ver, Agregar, Editar, Eliminar, Cargar / Importar) y un conjunto de recursos (Usuarios, Roles, Reglas, etc.). En la página web, busque el tipo de recurso asociado con esa página y luego verifique los permisos. Quizás algo como:

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

o

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

o incluso

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

Tiene un par de permisos que no tienen sentido con todo lo demás (Asignar permisos al usuario, por nombrar uno). No estoy seguro de cómo manejaría eso en base a lo poco que sé el problema.


Veo valores de al menos un puñado de enumeraciones diferentes allí ...

Mi primer pensamiento fue abordar el problema dividiendo los permisos en grupos lógicos ( RuleGroupPermissions , RulePermissions , LocationPermissions , ...) y luego tener una clase ( WebAgentPermissions ) exponiendo una propiedad para cada tipo de permiso enum.

Dado que los valores de los permisos parecen repetitivos, es probable que al final se salga con una sola enumeración:

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

Y luego WebAgentPermissions clase WebAgentPermissions exponga una propiedad para cada área donde se deben establecer los permisos;

class WebAgentPermissions { public Permissions RuleGroup { get; set; } public Permissions Rule{ get; set; } public Permissions Location{ get; set; } // and so on... }