ILBC 规范 2

接上篇 《ILBC 规范》  https://www.cnblogs.com/KSongKing/p/10354824.html  ,

ILBC    的 目标 是    跨平台  跨设备 。

D# / ILBC  可以 编写 操作系统 内核 层 以上的 各种应用, 

其实 除了 进程调度 虚拟内存 文件系统  外,  其它 的 内核 模块 可以用  D#  编写, 比如 Socket 。

D# / ILBC  的 设计目标 是 保持简单, 比如  D#  支持  Lambda 表达式,  但是  LinQ  应该 由 库 来 支持,  与 语言 无关 。

另一方面,  ILBC  不打算 发展一个 庞大 的 优化体系 。   C++ ,  .Net / C#  的 优化体系 已经 庞大复杂 到 成为 大公司 也很难 承受 之重 了 。

我们不会这么干 。

ILBC 认为   “简单 就是 优化”  。

保持 简单设计 和 模块化,  模块化 会 带来一些 性能损耗,  这些 性能损耗 是 合理 的 。

保持 简单设计 和 模块化,  对于  ILBC / D# / c3 / ……  以及 应用程序 都是 有益的 。

ILBC  的 目标 是 建立一个 基础设施 平台 。

就像 容器(比如 docker,    kubernetes),  容器 打算 在 操作系统 之上 建立一个 基础设施 平台,

我们的 做法 不同,

ILBC  是 用 语言 建立一个 基础设施 平台 。

为了 避开 “优化陷阱”, 我决定还是 启用 之前的 “ValueBox” 的 想法 。 ValueBox 的 想法 之前 想过, 但后来又放弃了 。

ValueBox 类似 java C# 里的 “装箱” 、 “拆箱” 。

ValueBox 就是 对于 int long float double char 等 值类型 (或者说 简单类型) , 用一个 对象(ValueBox) 装起来, 用于 需要 按照 对象 的 方式 处理 的 场合 。

本来我之前是放弃了 这个 想法, 觉得 还是 按照 C# 的 “一切都是对象” 的 做法, 让 值类型 也 作为 对象, 继承 Object 类, 然后 让 编译器 在 不需要 作为对象, 只是 对 值 计算 的 场合 把 值类型对象 优化回 值类型 (C 语言 里的 int long float double char 等) 。

但 现在 既然谈到 优化陷阱, 上面说的 “一切都是对象” 的 架构 就 有点 呵呵 了 。

这有一个问题, 把 值对象 优化回 值类型, 这个 优化 是 放在 C 中间代码 里 还是 InnerC 编译器 里,

放在 C 中间代码 是指 由 高级语言(D# c3 等) 编译器 来 优化, 这样 高级语言 编译 生成 的 C 中间代码 里面 就已经是 优化过的 代码, 比如 在 值 计算的 地方 就是 C 语言 的 int long float double char 等, 而不是 值对象 。

但 这样 要求 高级语言 的 编译器 都 按照 这个 标准 进行优化, 不然 在 各 高级语言 写的 库 之间 动态链接 时 会 发生问题 。

比如 D# 调用 c3 写的 库 的 Foo(int a) 方法, c3 做过优化, 所以 需要的 a 参数是 一个 C 语言 里的 int 类型, 而 D# 未作优化, 传给 Foo(int a) 的 a 参数 是 一个 int 对象, 这就 出错了, 这是 不安全的 。

但 要求 高级语言 的 编译器 都 按照 标准 优化, 这是一个比较 糟糕 的 事情 。

这会 让 高级语言 编译器 变得 麻烦 和 做 重复工作, 且 ILBC 会因 规则 累赘 而 缺乏活力 。

如果 把 优化 放在 InnerC 编译器 里 优化 , 那 会 和 我们的一些想法 不符 。 我们希望 InnerC 是一个 单纯的 C 编译器, 不要把 IL 层 的 东西 掺杂 到 里面 。

InnerC 是 一个 单纯的 C 编译器, 这也是 ILBC 的 初衷 和 本意 。

所以, 我们采用这样的设计, 值类型 就是 值类型, 对应到 C 语言 里的 基础类型(int long float double char 等), 值类型 不是 对象, 也不 继承 Object 类, 对象 是 引用类型, 继承 Object 类 。

当 需要 以 对象 的 方式来 处理 时, 把 值类型 包到 ValueBox 里 。

每个 值类型 会 对应一个 ValueBox, 比如 int 对应 IntBox, long 对应 LongBox, float 对应 FloatBox, double 对应 DoubleBox, char 对应 CharBox, bool 对应 BoolBox 等等 。

ValueBox 的 使用 代码 比如:

IntBox i = new IntBox( 10 ); // 10 就是 IntBox 包装的 Value

或者,

int i = 10;

IntBox iBox = new IntBox( i ); // 把 int 类型的 变量 i 的 值 包装到 IntBox

什么时候需要 把 值类型 包到 ValueBox 里 ? 或者说, 什么时候需要 以 对象 的 方式 来 处理 值类型 ?

一般是在 需要 动态传递参数 的 时候,

比如, Foo ( object o ) 方法 的 o 参数 可能 传入 各种类型, 那么可以把 o 参数 声明为 object 类型, 这样在 Foo() 方法内部 判断 o 参数 的 类型, 根据类型执行相关操作 。

又比如, 反射, 通过 反射 调用 方法, 参数 是 通过 object [ ] 数组 传入,

这 2 种 情况 对于 参数 都是 以 对象 的 方式 处理, 如果 参数 是 值类型 的话, 就需要 包装 成 ValueBox 再传入 。

D# / ILBC 支持 值类型 数组 、 值类型 泛型 容器 。

值类型 数组 就是 数组元素 就是 值类型, 假设 int 类型 占 4 个 字节, 那么 int [ ] 数组 的 每个元素 占用空间 也是 4 个 字节, 这和 C 语言 是一样的 。

值类型 泛型 容器 比如 List<int> , List<int> 的 内部数组 就是 int [ ] 。

值类型 数组, 值类型 泛型 容器 直接存取 值类型, 不需要 对 值类型 装箱 。

但是要注意, 比如 Dictionary<TKey, TValue> , value 可以是 值类型, 但 key 需要是 对象类型, 因为会 调用 key.GetHashCode() 方法 。

所以, 如果 key 是 值类型, 需要 装箱 成 ValueBox 。

比如

Dictionary < string , int > , value 可以是 值类型 ,

Dictionary < IntBox , object > , key 需要是 对象类型, 如果是 int , 需要 装箱 成 IntBox

如果声明 Dictionary < int , object > , 则 编译器 会对 key 的 类型 报错, 提示 应 声明 为 引用类型(对象类型) 。

值类型 又称 简单类型 ,

引用类型 又称 对象类型 ,

(这有点 呵呵)

编译器 是 依据 什么 检查 key 类型 应为 引用类型 呢 ?

我们可以在 D# 里 加入一个 语法, 比如, Dictionary 的 定义 是这样:

public class Dictionary < object TKey , TValue >

{

……

public void Add ( TKey key , TValue value )

{

int hash = key.GetHashCode() ;

……

}

}

可以看到, TKey 的前面 加了一个 object , 这表示 TKey 的 类型 应该是 object 类型 或者 object 的 子类,

这个 object 可以 换成 其它 的 类型, 比如 其它 的 类 或者 接口 。

这样的话, 如果 TKey 被 声明 为 值类型, 比如 Dictionary < int , object > , 由于 int 不是 引用类型, 当然 也就不是 object 或者 object 的 子类, 于是 不满足 TKey 的 类型约束, 于是 编译器 就 报错了 。

如果 TKey 的 前面 不声明 object , 会怎么样 ? 还是会报错 。

因为在 Add ( TKey key , TValue value ) 方法 里 调用了 key.GetHashCode() 方法, 调用方法 意味着 必须是 引用类型(对象类型), 所以 编译器 会要求 Dictionary 的 定义 里 要 声明 TKey 的 类型 , 且 TKey 的 类型 必须是 引用类型(对象类型) 。

这 也有点 呵呵 。

IntBox override(重写) 了 Object 类的 GetHashCode() 方法, 用于 返回 IntBox 包装的 int 值 的 HashCode, 不过 int 类型 的 GetHashCode() 方法 可能是 最简单的了, 直接返回 int 值 就可以 。 ^^

String 类 会 override(重写) Object 类 的 Equals(object o) 方法, 并且会 增加 一个 Equals(string s) 方法, Equals( object o ) 方法内部会调用 Equals( string s ) 方法 。 Equals ( object o ) 方法 先 判断 o 是不是 String 类型, 如果不是, 则 返回 false, 如果是, 则 调用 Equals( string s ) 判断 是否相等 。

D# 里 用 “ == ” 号 比较 2 个 String 的 代码 会被 编译器 处理成 调用 Equals( string s ) 方法 。

除了 最底层 的 模块 用 C 编写, D# / ILBC 可以编写 各个层次 各个种类 的 软件 ,

用 C 写 可以用 InnerC 写, 只要 符合 ILBC 规范, InnerC 写的 代码 就可以 和 ILBC 程序集 同质链接 。

从这个 意义 来看, ILBC / InnerC 可以 编写 包括 操作系统 在内 的 各个层次 各个种类 的 软件 ,

从这个 意义 来看, ILBC 是 一个 软件 基础设施 平台 。

今天 看了 C# 8.0 新特性 https://mp.weixin.qq.com/s?__biz=MzAwNTMxMzg1MA==&mid=2654074187&idx=1&sn=e0a6d9c963c3405dcae232a70434f225&chksm=80dbd11eb7ac58085d5357785cae13bbd4a3ccf92e876cd12c1f8faa9ada7629e5f8b2ff030e&mpshare=1&scene=23&srcid=#rd ,

可以看出, C# 8.0 标志着 C# 开始成为 “保姆型” 语言 , 而不是 程序员 的 语言 。

D# 将 一直 会是 程序员 的 语言 , 这是 D# 的 设计目标 和 使命 。

补充一点, ValueBox 的 使用 小技巧 ,

在一段代码中, ValueBox 可以只 new 一个, 然后 重复使用 。

ValueBox 有一个 public value 字段, 就是 ValueBox 包装的 值, 对 value 字段 赋上新值 就可以 重新使用 了 。

比如, IntBox ,有 public int value 字段,

IntBox i = new IntBox( 1 );

i.value = 2;

i.value = 3;

i.value = 4;

重复使用 ValueBox 可以 减少 new ValueBox 和 GC 回收 的 开销 。

有 网友 提议 D# 的 名字 可以叫 Dava , 这名字 挺好听, 挺美丽的, 和 女神(Diva) 相近, 好吧, 就叫 Dava 吧, D# 又名 Dava 。

接下来 我们 讨论 泛型 原理 / 规范 ,

泛型 在 ILBC 里 和 C++ 类似 , 由 高级语言 编译器 生成 具体类型,

假设 有 一个 List<T> 类, 这个类 的 C 中间代码 如下:

struct List<T>

{

T arr [ 20 ] ; // 20 是 内部数组 的 初始化 长度

int length = 0 ;

}

void List<T><>Add<>T ( List<T> * this , T element )

{

this -> arr [ this -> length ] = element ;

this -> length ++ ;

}

T List<T><>Get<>T ( List<T> * this , int index )

{

return this -> arr [ index ] ;

}

如果在 代码 中 使用 了

List<int> list1 = new List<int>();

List<string> list2 = new List<string>();

那么 编译器 会 为 List<int> 生成一个 具体类型 List~int 类, 也会为 List<string> 生成一个 List~string 类 , 代码如下:

struct List~int

{

int arr [ 20 ] ; // 20 是 内部数组 的 初始化 长度

int length = 0 ;

}

void List~int<>Add<>int ( List~int * this , int element )

{

this -> arr [ this -> length ] = element ;

this -> length ++ ;

}

int List~int<>Get<>int ( List~int * this , int index )

{

return this -> arr [ index ] ;

}

struct List~string

{

string * arr [ 20 ] ; // 20 是 内部数组 的 初始化 长度

int length = 0 ;

}

void List~string<>Add<>string ( List~int * this , string * element )

{

this -> arr [ this -> length ] = element ;

this -> length ++ ;

}

int List~string<>Get<>int ( List~int * this , int index )

{

return this -> arr [ index ] ;

}

可以看出来, 把 泛型类型 里的 List<T> 替换成 具体类型(List<int>, List<string>), 把 T 替换成 泛型参数类型 (int , string *) 就是 具体类型 。

注意 , 值类型 把 T 替换为 值类型 就可以, 比如 int, 引用类型 要把 T 替换成 引用(指针), 比如 string * 。

这部分 由 高级语言 编译器 完成 。

复杂一点的情况是, 跨 程序集 的 情况, 假设 有 程序集 A , B , A 引用了 B 里的 List<T> , 那 …… ?

这个需要 把 List<T> 的 C 中间代码 放在 B 的 元数据 文件 (B.ild) 里, A 引用 B.ild , 编译器 会 从 B.ild 中 获取到 List<T> 的 C 中间代码, 根据 List<T> 的 C 中间代码 生成 具体类型 的 C 中间代码 。

这好像 又 有点 呵呵 了 。

不过 这样看来的话, 上文 关于 泛型 对 值类型 和 引用类型 的 不同处理 好像 没必要了 。

上文 举例 的 Dictionary<object TKey , TValue> 要把 TKey 声明为 object ,

这其实已经没必要了 。

public class Dictionary < TKey , TValue >

{

……

public void Add ( TKey key , TValue value )

{

int hash = key.GetHashCode() ;

……

}

}

如果在 代码 中 写了

Dictionary< int , object > dic ;

则 编译器 会 报错 “TKey 类型 不能是 值类型, 应该是 引用类型(对象类型), 因为 Dictionary < TKey , TValue > 对 TKey 调用了方法, 值类型 不支持 调用方法 。”

假设 有 class Foo<T> , 代码如下:

class Foo<T>

{

void M1 ( T t )

{

t.Add();

}

}

Foo<A> foo = new Foo<A>();

A a = new A();

foo.M1 ( a ) ;

A 是 引用类型(对象类型), 如果 A 没有 Add() 方法, 编译器 会 报错 “泛型参数类型 A 不包含 Add() 方法 。”

我们还可以把 代码 改成:

class Foo<T>

{

T M1 ( T t )

{

return t ++ ;

}

}

Foo<int> foo = new Foo<int>();

int i = 0 ;

int p = foo.M1 ( i ) ;

这 可以 编译 通过, 因为 int 支持 ++ 运算符, 实际上, 只要 支持 ++ 运算符 的 类型 都可以 使用 Foo<T> , 或者说, 只要 支持 ++ 运算符 的 类型 都 可以作为 Foo<T> 的 泛型参数类型 T 。

猜你喜欢

转载自www.cnblogs.com/KSongKing/p/10440001.html