En el Post de hoy voy a hacer mención a una funcionalidad de C# que sinceramente, a mi se me había olvidado, la ví por primera vez en mi primer trabajo allá por 2014/2015 y no la había vuelto a ver hasta hace un par de semanas.
En ese momento, me saltó la chispa y dije, porqué no hacer un post sobre Flags en C#.
1 - Qué es el atributo flag de C# y para qué sirve?
Bueno el atributo flag
es un atributo que se le pone a un enum para convertirlo en bits, eso quiere decir, que, cada uno de los valores representa un valor de bits.
En otras palabras, que cuando utilizamos el atributo en un enum, este se nos traduce a binario:
[Flags]
public enum TestFlags
{
FirstValue = 00,
SecondValue = 01,
ThirdValue = 10
}
En este ejemplo, los valores serían equivalentes a:
FirstValue = 00000,
SecondValue = 00001,
ThirdValue = 00010
Nota: técnicamente podemos usar Binary literals 0b0000
, pero al final es lo mismo.
O lo que es lo mismo en un valor numérico:
FirstValue = 0,
SecondValue = 1,
ThirdValue = 2
Y claro, visto esto, te puedes preguntar, pero ¿ para qué sirve realmente, o cual es la utilidad real del atributo flag?
2 - Caso de uso real del atributo Flag de c#
La forma más sencilla de ver como funciona es con un caso de uso real, que es el caso particular donde yo vi las flags hace 8 años, los permisos de usuario. Vamos a suponer que tenemos los siguientes permisos para cierta funcionalidad:
- None
- View
- Create
- Edit
- Delete
Tenemos una web, donde, tenemos diferentes acciones. Técnicamente no hace falta poner el “none” pero por claridad, durante la explicación, en mi opinión, se ve más fácil.
Estas acciones las vamos a pasar a nuestro enum y les vamos a asignar un valor, como hemos visto antes lo podemos asignar utilizando el valor del entero:
[Flags]
public enum Permissions
{
//None = 0, //0b0000
View = 1, //0b0001
Create = 2, //0b0010
Edit = 4, //0b0100
Delete = 8 //0b1000
}
Estamos acostumbrados a que con los enum en C# solo se puede tener un valor asignado, si continuamos con este ejemplo, tendremos permisos de editar, y tenemos asignado el valor 4. Esto quiere decir que podemos crear y ver?
Bueno pues claramente si tenemos un if que hace if(user.permissions == Permissions.Create)
nos va fallar, porque tenemos un 4;
Una forma de arreglar esto, sería comprobando mayor y menor, pero entonces alguien que pueda borrar puede crear y editar y quizá tu lógica no espere eso.
Aquí es donde viene el verdadero motivo por el que el atributo flag es útil.
Estamos trabajando con binario por lo que tenemos bits que nos dicen si algo esta habilitado o no. Por ejemplo, si podemos ver, editar y crear, tendremos las 3 primeras opciones, que suman 7, y 7 en binario es 0b0111
como puedes ver son los bits, individuales de cada una de las opciones, y estos, no tienen conflicto entre ellos.
Si por ejemplo, podemos ver y borrar, pero no editar ni crear, tendremos el valor de 0b1001
que es 9.
Ahora que tenemos esto claro, solo nos queda entender cómo asignamos los valores, lo cual lo hacemos con el bitwise operator:
Permissions userPermissions = Permissions.View | Permissions.Delete;
Y luego comparamos con el método .HasFlag(...)
if (userPermissions.HasFlag(Permissions.View))
{
//Then we can see the page;
}
if (userPermissions.HasFlag(Permissions.Create))
{
//then the user can see the create button
}
Y como podemos ver, funciona a la perfección. En mi opinión, es una de las mejores, si no la mejor de tratar permisos, porque nos permite incluir mucha variedad de permisos combinados de una forma muy sencilla.
Antes de acabar, si hacemos .ToString()
nos lo devuelve las flags que tenemos activadas como un string con los valores separados por comas. Si hacemos un cast a int
, nos da el valor. Cuando leemos de la base de datos pues tenemos que saber cómo hemos guardado, para hacer el mapeo de vuelta.