前言

以前就有提过 Enum Flags,但平日不常用。最近翻 Angular 源码,发现它很多地方用到,而且没有封装语义代码。一堆符号真的看不惯啊...

于是又去复习了一遍,熟悉熟悉。顺便写一篇做记入呗。

这篇 C# 和 TypeScript 会一起讲。

参考

深入理解 TypeScript – 枚举

Enum, Flags and bitwise operators (中文版这篇)

Stack Overflow – What does the [Flags] Enum Attribute mean in C#?

W3Schools – JavaScript Bitwise Operations

介绍

Enum 大家都很熟悉了。它就是 number(也有些场景会用 string 啦,但这篇我们 focus number 就好)

enum Status {
Status0,
Status1,
Status2
} console.log(Status.Status0); // 0
console.log(Status.Status1); // 1
console.log(Status.Status2); // 2

那...如果我们要 Enum List 怎么写?

我们当然可以用 Array。

const statusList: Status[] = [Status.Status0, Status.Status1];
if(statusList.includes(Status.Status0)) {
// included
}
if(statusList.some(e => e === Status.Status0)) {
// included
}

但是有一种性能超级快的方法,可以取代 Enum Array,那就是利用二进制操作符。

而这就是 Enum Flags 的核心。

<< | & ~ ^ 位运算符

我们先来学 5 个和二进制相关的操作符,Enum Flags 就是利用这些原理实现的。

Bitwise left shift 位运算符 <<

Bitwise 是"按位"(按照位置)的意思,Bitwise Shifts 就是按位移动。

下面是 1, 2, 4, 8, 16 的二进制

console.log(Number(1).toString(2).padStart(5, '0'));  // 00001
console.log(Number(2).toString(2).padStart(5, '0')); // 00010
console.log(Number(4).toString(2).padStart(5, '0')); // 00100
console.log(Number(8).toString(2).padStart(5, '0')); // 01000
console.log(Number(16).toString(2).padStart(5, '0')); // 10000

注意它们的规律,1 的位置是一条斜线。

这种情况下, 我们可以使用 << 符号来替代自己做乘法

console.log(Number(1 << 0).toString(2).padStart(5, '0'));  // 00001
console.log(Number(1 << 1).toString(2).padStart(5, '0')); // 00010
console.log(Number(1 << 2).toString(2).padStart(5, '0')); // 00100
console.log(Number(1 << 3).toString(2).padStart(5, '0')); // 01000
console.log(Number(1 << 4).toString(2).padStart(5, '0')); // 10000

1 << 2 的的过程是这样的

1 的二进制是 1

往左边移动 2 个位置就变成了 00100 也就是十进制的 4,或者你也可以换一个角度理解,<< 2 就是在结尾加 2 个零。

1 << 3 就移动 3 个位置,变成 01000 就是 8

1 << 4 就移动 4 个位置,变成 10000 就是 16

所以,可以这样玩。

1 << 0 === 1;
1 << 1 === 2;
1 << 2 === 4;
1 << 3 === 8;
1 << 4 === 16;
1 << 5 === 32;

Bitwise OR 位运算符 |

console.log(binary(2));     // 00010
console.log(binary(4)); // 00100
console.log(binary(2 | 4)); // 00110

它的逻辑是按位对比,只要其中一个是 1 那么就是 1。

最后一位是 0 0 所以是 0

最后第二位是 1 0 所以是 1

看看更多例子

最后第三位是 0 1 所以是 1

Bitwise AND 位运算符 &

And 和 Or 都是位置对比,区别是

Or 只要求其中一个是 1 那就是 1

And 要求全部是 1 那才是 1

比如

2 | 4       // 00110
2 // 00010
(2 | 4) & 2 // 00010

再来一个

// 2 | 4 | 8               // 01110
// 8 | 16 // 11000
// (2 | 4 | 8) & (8 | 16) // 01000

Bitwise NOT 位运算符 ~

~ 是取反,把 0 变成 1,把 1 变成 0。

例子: ~5

首先 5 的二进制是 101

Console.WriteLine(Convert.ToString(5, 2)); // 101

注: 我这里用 C#。TypeScript 有点不同,下面会讲

把 101 写满 32 位是

Console.WriteLine(Convert.ToString(5, 2).PadLeft(32, '0')); // 00000000000000000000000000000101

手动取反

Console.WriteLine(Convert.ToString(5, 2).PadLeft(32, '0').Replace("0", "z").Replace("1", "0").Replace("z", "1")); // 11111111111111111111111111111010
// 对比
// 00000000000000000000000000000101
// 11111111111111111111111111111010

转换回十进制

var value = Convert.ToString(5, 2).PadLeft(32, '0').Replace("0", "z").Replace("1", "0").Replace("z", "1");
Console.WriteLine(Convert.ToInt32(value, 2)); // -6
Console.WriteLine(~5); // -6

和 ~5 相等。

JavaScript 有点特别,因为它的二进制可以是负数(原理我没有 research, 想懂更多可以看这篇: 知乎 – 还搞不懂负数怎么用二进制表示?看完这一篇就懂了

// C#
Console.WriteLine(Convert.ToString(-6, 2)); // 11111111111111111111111111111010 // JS
console.log(Number(-6).toString(2)); // -110

虽然二进制写法不同,但 ~ 的操作结果是一致的,~5 === -6(十进制)

Bitwise XOR 位运算符 ^

XOR 在对比时,如果完全相等返回 0,其中一个不相等返回 1。

比如一列都是 0,或者一列都是 1。就是相等,返回 0。

一列里有 1 又有 0,那就不相等,返回 1。

console.log(1 ^ 1); // 相等 0
console.log(0 ^ 0); // 相等 0
console.log(0 ^ 1); // 不相等 1
console.log(1 ^ 0); // 不相等 1 console.log(4 ^ 8) // 12;
console.log(Number(4).toString(2)); // 0100
console.log(Number(8).toString(2)); // 1000
// 对比
// 0100
// 1000
// 1100 结果: 不等, 不等, 相等, 相等
console.log(parseInt('1100', 2)); // 12 证明 4 ^ 8 = 12

Enum Flags 使用

Enum Flags 就是配合上面这些位运算符来使用的。具体原理我就不详细解释了。

有兴趣的可以自己一步一步转换二进制。依据上面位运算符的规则来推演。

定义 Enum Flags

enum StatusFlags {
one = 1 << 0, // 1 (00001)
two = 1 << 1, // 2 (00010)
four = 1 << 2, // 4 (00100)
eight = 1 << 3, // 8 (01000)
sixteen = 1 << 4 // 16 (10000)
}

定义 Enum Flags 有一个规则,号码一定要是 2 的幂次方。

可以直接写 2, 4, 8, 16。

也可以利用 << 位运算符。

甚至像 Angular 源码中,用十六进制也是可以的。(只要是 2 的幂次方就可以了)

Enum List

const statusList1 = StatusFlags.one | StatusFlags.two | StatusFlags.four; // 7 (00111)
const statusList2 = StatusFlags.one | StatusFlags.sixteen; // 17 (10001)

利用 Bitwise OR 把 enum 串起来就是 enum list。

Add enum to list

let statusList1 = StatusFlags.one | StatusFlags.two | StatusFlags.four; // 7 (00111)

statusList1 |= StatusFlags.sixteen; // add more enum (已经有了会 ignore)

statusList1 === (StatusFlags.one | StatusFlags.two | StatusFlags.four | StatusFlags.sixteen); // true

用 Bitwise OR,如果 list 里面已经有了也是可以运行,不会有 duplicated 概念的。

Remove enum from list

statusList1 &= ~StatusFlags.two; // remove enum(本来就没有会 ignore)

statusList1 === (StatusFlags.one | StatusFlags.four); // true

用了 Bitwise AND 和 Bitwise NOT。

Toggle enum from List

let statusList1 = StatusFlags.one | StatusFlags.two | StatusFlags.four; // 7 (00111)

statusList1 ^= StatusFlags.one; // toggle enum (有就移除)
statusList1 === (StatusFlags.two | StatusFlags.four); statusList1 ^= StatusFlags.one; // toggle enum (有就移除)
statusList1 === (StatusFlags.one | StatusFlags.two | StatusFlags.four);

用 Bitwise XOR 可以实现 toggle。

Included in list

判断 list2 是否 "全部" 在 list1 里

const statusList1 = StatusFlags.one | StatusFlags.two | StatusFlags.four; // 7 (00111)
const statusList2 = StatusFlags.one | StatusFlags.two; // 17 (10001) console.log((statusList1 & statusList2) === statusList2); // true, all list2 in list1

判断 list2 是否 "某一个" 在 list1 里

const statusList1 = StatusFlags.one | StatusFlags.two | StatusFlags.four; // 7 (00111)
const statusList2 = StatusFlags.one | StatusFlags.sixteen; // 17 (10001) console.log((statusList1 & statusList2) > 0); // true, some list2 in list1

总结

Enum List 是利用各种位二进制运算符来实现类似 Array 效果的。

它本质上只是数字而已。

Enum Flags (C#)

和 TypeScript 的 Enum Flags 原理是一样的.

[Flags] // 最好添加标签
public enum StatusFlags
{
One = 1 << 0,
Two = 1 << 1,
Four = 1 << 2,
Eight = 1 << 3,
Sixteen = 1 << 4
} public static class Program
{
public static void Main()
{
var statusList1 = StatusFlags.One | StatusFlags.Two | StatusFlags.Four;
var statusList2 = StatusFlags.One | StatusFlags.Sixteen; statusList1 |= StatusFlags.Sixteen; // add
statusList1 &= ~StatusFlags.Sixteen; // remove
statusList1 ^= StatusFlags.Sixteen; // toggle var allIn1 = (statusList1 & statusList2) == statusList2; // true
var allIn2 = statusList1.HasFlag(statusList2); // HasFlag 和上一行是等价的, list2 完全在 list1 内 var someIn = (statusList2 & statusList1) > 0; // true
}
}

HasFlag 是一个 build-in 的方法。

C# and TypeScript – Enum Flags的更多相关文章

  1. enum flags 枚举Flags标示 位运算符

    C#位运算 详见:http://wenku.baidu.com/link?url=4KUNkwuVTm8dwhmPLXeyYytmnDOJnmLG1L2C77kP-rY0On33ksQxwchoTlq ...

  2. TypeScript enum 枚举实现原理

    TypeScript enum 枚举实现原理 反向映射 https://www.typescriptlang.org/docs/handbook/enums.html enum Direction { ...

  3. [TypeScript] Collect Related Strings in a String Enum in TypeScript

    As of TypeScript 2.4, it is now possible to define string enums, or more precisely, enums with strin ...

  4. TypeScript之基本数据类型

    前言 最近项目很急,所以没有什么时间回答关于Xamarin.Android方面的问题,也有一段时间没有更新.主要是手头很缺人,如果有谁有兴趣加入我们的话,可以私聊我,这样我就能继续造福社区了,同时还有 ...

  5. 基本数据类型TypeScript

    TypeScript 前言 最近项目很急,所以没有什么时间回答关于Xamarin.Android方面的问题,也有一段时间没有更新.主要是手头很缺人,如果有谁有兴趣加入我们的话,可以私聊我,这样我就能继 ...

  6. 10 Essential TypeScript Tips And Tricks For Angular Devs

    原文: https://www.sitepoint.com/10-essential-typescript-tips-tricks-angular/ ------------------------- ...

  7. Typescript使用字符串联合类型代替枚举类型

    TypeScript宗旨 我觉得Typescript的宗旨是 任何一个 TypeScript 程序,在手动删去类型部分,将后缀改成 .js 后,都应能够正常运行.Typescript是javascri ...

  8. Asp.net core Enum as string + ef core value convertor

    更新 : 2019-06-08 build in convertor https://docs.microsoft.com/en-us/ef/core/modeling/value-conversio ...

  9. C#:USB设备枚举 --转自CSDN作者:Splash

    (一)DeviceIoControl的PInvoke /* ---------------------------------------------------------- 文件名称:Device ...

  10. YUV420查表法高效、无失真的转换为RGB32格式

    YUV格式有两大类:planar和packed.planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V,这里所讲述的就是这中存储格式的:packed的YUV ...

随机推荐

  1. BootstrapTable 行内编辑解决方案:bootstrap-table-editor

    最近开发的一个业务平台,是一个低代码业务平台.其中用到的了bootstrap-table组件.但是bootstrap-table自身不带编辑功能. 通过搜索发现,网上大部分的解决方案都是使用x-edi ...

  2. php.ini文件与php.d

    `php.ini` 是 PHP 的主要配置文件,用于全局配置 PHP 的行为和功能.它包含了许多 PHP 的核心设置,如内存限制.错误报告级别.扩展加载等. `php.ini` 文件通常位于 PHP ...

  3. [oeasy]python0028_直接运行_修改py文件执行权限_设置py文件打开方式

    ​ 直接运行 回忆上次内容 我们把两个程序整合起来了 可以持续输出当前时间 每秒都更新 ​ 编辑 但是我想在 shell 里面 只输入文件名(./sleep.py)并回车 就能不断输出时间 可能吗? ...

  4. Python elasticsearch-py类库基础用法

    实践环境 https://pypi.org/project/elasticsearch/ pip install elasticsearch==7.6.0 离线安装包及依赖包下载地址: https:/ ...

  5. 关于构建一个可视化+code系统的思路

    思路是有参考UE的现有功能,加之前的逻辑. 大概分为三个模块: 底层, 即native层 ,这一层实际上分为三个部分:  1.GUI层的解析,2.数据存储   3.Code的解析 这三部分关键在于他们 ...

  6. LeetCode860. 柠檬水找零

    题目链接:https://leetcode.cn/problems/lemonade-change/description/ 题目叙述: 在柠檬水摊上,每一杯柠檬水的售价为 5 美元.顾客排队购买你的 ...

  7. M1 Mac安装anaconda3

    1.正常安装 首先进入官网https://www.anaconda.com/ 下载,安装 自行大胆的安装 2.环境配置 直接安装完成后,代码文件的存储路径为默认路径,为了更好的管理代码文件我们需要更换 ...

  8. Win11不在C盘安装WSL2(Linux环境),安装Nvidia驱动和默认使用Win11的网络代理服务

    众所周知,WSL 2 为 Windows 用户提供了一个强大.高效且灵活的 Linux 环境,特别适合开发者使用.它结合了 Windows 和 Linux 的优点,为用户提供了更加全面和高效的工作环境 ...

  9. 【C3】06 选择器概述

    CSS中,选择器用来指定网页上我们想要样式化的HTML元素. CSS选择器有很多种可供使用,所以在选择要样式化的元素时,我们可以做到很精细的地步. 本文和本文的子篇中,我们将会很详细地讲授选择器不同的 ...

  10. 【转载】 ubuntu18.04/22.04 cmake版本 更新方法

    版权声明:本文为CSDN博主「问题生产商」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/github_3958 ...