D# 语法

这篇文章 随意 的 记录 关于 D# 语法的想法 。javascript

 

有关   ILBC / D# , 见 《ILBC 白皮书》   http://www.javashuo.com/article/p-bsuuysoc-bo.html    。html

 

 

D#  又名  Data ,  D++      。前端

 

D#  支持 结构体 方法 和 继承 ,  D# 的 结构体 的 静态方法 和 类 同样, 实例方法 所有采用 扩展方法 的 方式 定义 。java

扩展方法 就是 C# 里的 扩展方法 的 那种 扩展方法   。git

在一个 静态方法 的 第一个参数 传入 结构体 自身,    这就是 实例方法  。程序员

D# 结构体 实例方法 的 定义 比 C# 扩展方法 简单,  不须要在 第一个 自身参数 前 加  “this”  关键字  。github

好比, A 结构体,数据库

 

struct A编程

{json

          public static  void Foo( A a )

          {

                         // do something

          }

}

 

能够这样调用 :

 

A a;

……

a.Foo();

 

还能够 经过 指针 的 方式 传递 自身参数,  好比 :

 

struct A

{

         int     i;

          public static  void Foo( A  *   a )

          {

                         int   j   =  a ->  i  ;

 

                         // do something

          }

}

 

能够这样调用 :

A a;

( & a ) ->  Foo()   ;

 

或者 :

A * a;

a   ->   Foo()   ;

 

嘿嘿嘿,     指针 的方式能够用于 体积 比较大 的 结构体 。   总之 D# 提供了  灵活性   。

 

更灵活的是,  D# 容许 结构体 同时定义 按值传递 和 按指针传递   2 个 重载 的 实例方法,就是 :

 

 

struct A

{

          int     i;

 

          public static  void Foo( A a )

          {

                         // do something

          }

 

          public static  void Foo( A  *   a )

          {

                         int   j   =  a ->  i  ;

                         // do something

          }

}

 

能够看到,    Foo( A a )   和   Foo( A * a )      能够共存,    是  重载   。

 

固然, 指针操做 是 unsafe 操做,   全部的 指针 操做 都应该 用 unsafe 关键字 修饰,   因此 上述代码 应该 加上 unsafe 关键字 ,以下:

 

struct A

{

          int     i;

 

          public static  void Foo( A a )

          {

                         // do something

          }

 

          unsafe   public static  void Foo( A  *   a )

          {

                         int   j   =  a ->  i  ;

                         // do something

          }

}

 

看 ,   在 Foo( A * a )    前面 加上了   unsafe  关键字,      对于  C# , unsafe 代码 应包括在 unsafe 块  中,

D# 简化了 这个 作法,  不使用 unsafe 块,   而是 用 unsafe 关键字 修饰 方法,

也能够  修饰 类,     修饰 类 表示 整个类 都是 unsafe,  就不须要 每一个 方法 都用 unsafe 修饰 。

 

unsafe  没有 实际意义,   只有 标记意义  。

unsafe 的 做用 就是 能够经过 编译器 查找 全部的 unsafe 方法 类,   让 程序员 能够容易的关注 unsafe 代码   。

 

为何 要 支持   结构体 和 unsafe  ?     由于 我以前在 《ILBC 规范》 中 说过,   D# 的 设计目标 之一 是 执行效率  。

 

具体的说,  D# 的 结构体 和 unsafe  主要会 用来 支持   底层应用,   基础应用,   高性能中间件      。

除了 进程(线程)调度  、 IO 内核模块 之外 ,    包括 套接字(Socket) 在内 的 底层模块 均可以用 D# 编写  。

事实上   只要 不使用 堆(new) 的话,   进程(线程)调度 、 IO 内核模块 也能够用 D# 编写,

这种 状况 基本上 所有是 unsafe  代码 和  结构体,       这种状况 差很少 和 C 等价  。

但 这样的话,好像 还要 加一个 “开关”,  好比 编译选项 什么的,     在    “不使用 堆 的 模式 下”   编程(编译)  。

这会让 一个 语言 变得 复杂,      不太 符合 D# 的 初衷   。

实际上  进程(线程)调度  、 IO 内核模块    这就是 C 的 事,不必 去和 C 抢  。   呵呵呵呵  。

 

C++  就是 那种   “只要 你 选择,  什么均可以作”   的 语言,  因而 呵呵 了   。

这就好像 有的 业务系统 写的 很灵活,   什么均可以作, 只要 你 去 配置,,,,,,

而后,灵活过头了, 一堆配置,  等你学会 这堆 配置 , 也 差很少 成 这个 系统 的 专家 了 。   问题是 这个 系统 的 专家 只是在 你的 公司 里 有用, 外面的 业界 对 此 没什么 承认    ,     而后 就 呵呵 了     。

 

那 什么 是 基础应用 呢 ?  好比    图形图像,2D 3D,游戏,  视频音频, 视频采样,音频采样,人工智能,大数据,数据库,文档数据库,大规模并行计算文档数据库,搜索引擎,爬虫,编译器  , 文本编辑器,  大型文本编辑器     ,,,

 

什么事 高性能中间件  ?  好比 Nginx  ,    上面 基础应用 中 提到的  文档数据库 之类 好像也能够算   中间件   。  还有 搜索引擎 也算 中间件 ?    不知道   ……

 

结构体 由于 没有 GC ,   因此  难以 应用于  对象图(结构体 引用 图),    可是 单纯 在 栈 中 的 结构体 的 应用范围 也颇有限 。

可是,  有一种 场合,  结构体 能够 获得 充分 的 使用,   就是 结构体 数组,

或者说,   一个 很大 的 数组 中 包含了 不少 的 结构体 。

这在 基础应用 中 很普遍,

好比 上面说的    图形图像,2D 3D,游戏,  视频音频, 视频采样,音频采样  ,,,,,,,

乃至于      人工智能,大数据,数据库,文档数据库,大规模并行计算文档数据库,搜索引擎,爬虫,编译器  , 文本编辑器,  大型文本编辑器        都 与此有关  。

 

D#   对 结构体 和 指针 的 支持  很是 适合于 这些 场景   。

 

数组 自己 是 属于 堆 的,  可是 数组 里 包含了 大量 的 结构体   。

基础应用 须要 对 这种 场景 很是良好 的 支持   。

因此 从 这里 能够看到,   D# 的 意义 之一,  开发 基础应用 不必定 要用 C++  ,  如今 的 情况 就是 基本都用  C++   。

包括   图形图像  视频音频 人工智能   。

 

因此   D#   须要 对 数组 进行 比较 大力 的 支持,  包括 排序  等 , 其它 的 还有什么?    想不出来,  你们想到能够告诉我  。

C#  对 集合类 好比 List<T>  的 支持不少, 我也不太清楚,反正有不少 方法 和 扩展方法  。

 

不过 D#  首要支持 的是 数组, 别跟我说 什么 List<T>  ,   C#  都 在 推   Span,  ArraySegment  了,  

D#  没有那么多 麻烦,    直接   数组 。

 

从 C# 的 Span, ArraySegment,  ArrayPool,  BufferManager     这一堆东西 能够 看出来,  C#  目前 的 尴尬  。

这一堆东西 每个 都是 一套复杂的班子,   我又想起 前几天 我查了一下 C# 的元组,  其实我须要的 只是  ( int i ,  string s )    这样一个 小括号 而已,

结果 查出了 一大堆   名字空间 很长的东西, 里面好像有  “Tuple”  字样 , 又是 泛型, 又是 继承,  好像 Visual Studio 还要装什么 安装包,

而后  。

呀,  我怎么 不知不觉  的 又 批评 C# 一下 了?

 

D# 对于 数组 的 操做 好比 排序 什么的,  能够在 指定 的 范围(Range) 内 进行,   好比 数组 有  1000 个元素, 我只对 第 200 ~ 400 个 元素 排序,  这只须要 在 调用 Sort()  方法 时  指定 开始 index 和 结束 index 就能够了,好比:

 

int []    iArr   ;

……

iAr.Sort( 200 ,   400 )   ;

 

若是 实在 要 封装,本身 弄个 结构体 或者 对象 把 开始 index 和 结束 index 包装起来就行,好比:

 

struct    Span

{

            int   beginIndex,

            int   endIndex

}

 

void Foo( Span span )

{

          int []    iArr   ;

          ……

          iAr.Sort( span.beginIndex ,    span.endIndex )   ;

}

 

面向对象  爱好者 们 可能 喜欢 把 Sort()  也  封装到 Span 里,

 

class ArraySpan<T>

{

             private T  []       arr      ;

             private   int  beginIndex    ;

             private   int  endIndex     ;

 

             public   ArraySpan(T []   arr,   int  beginIndex,     int  endIndex)

             {

                        this.arr = arr;

                        this.beginIndex =  beginIndex ;

                        this.endIndex = endIndex ;

             }

 

             public Sort()

             {

                           this.arr.Sort( this.beginIndex,    this.endIndex )    ;

             }

}

 

这也能够,   不过 这是 程序员 的 事,不是 语言 的 事,   或者说 这是 库 的 事,  程序员 能够把 本身的 封装 贡献出来,   做为 库  。

 

另外一方面 ,  List<T>  固然也要, 不只 List, 还有 Dictionary,  Queue,   LiinkedList,   Stack    这些都要,  D# 固然也要有 一个 System.Collections   。

不过 大量 数据 的 存储操做 仍然是 数组   。

能够用 List  来存放 多个 数组  。

 

通过一番思索(过程 略),  我决定这样来设计  D#  的 数组 :

D#  有 2 种 数组,   unsafe 数组 和 托管数组,     unsafe 数组 也叫 原生数组,  就是一个 C 语言 数组, 也能够说是 数组首地址,或者 指向 某个元素 的 指针,

托管数组 是一个 对象, 内部 包含了一个 unsafe 数组,  注意,  重点来了,

托管数组 是 用 泛型 实现的,   托管数组 是一个 泛型类,  

这样,  对于 多维数组,  咱们 须要像 C# 的 Action 同样,   预先定义好  多个  数组类型,   

 

就像 C# 的 Action 有 这些 定义 :

Action,  Action<T>,  Action<T1, T2>,    Action<T1, T2, T3>,     Action<T1, T2, T3, T4>  ,     ……

 

D# 的 会 定义这些 数组类型 :

Array1<T>,   Array2<T>,    Array3<T>,    Array4<T>,    Array5<T>,      ……

Array1 是 一维数组, Array2 是 二维数组, Array3 是 三维数组,   ……

 

D# 容许 给 索引器(中括号 [   ]   经过 逗号 隔开 传入 多个 参数),

好比 

 

Array2<int>   iArr = new  Array2<int>()   ;

iArr  [  2,  3  ]    =   10   ;

 

这样的话, 实际上,  程序员 彻底 能够 本身 来 实现 托管数组,   能够自由的 实现  n 维 的 托管数组,

也就是说,  托管数组  不是 语言 的 事,  是 库 的 事,   程序员 能够 自由 的 实现 托管数组,  能够把本身 实现 贡献 出来,  做为 库 。

社区 里 能够 有 多种 托管数组 的 实现(库),   程序员 能够 任意选择 使用,  更能够 本身实现 贡献 。

这就是    “库”   的    理念 。

这是一种 简洁 开放 的 语言 理念  。

 

固然,   unsafe 数组 由  D# 提供  。

 

这样的作法 有点可笑,  可是 很爽  。

 

咱们 再 回过头来 看看 C#  的 数组, C# 有一个 Array 类, 内部 作了 不少工做, 可是这些工做 程序员 很难了解,  也不能参与, 这就是 C# 常常挥舞 的 ”CLR 技术“ 。

 

咱们再来看看 数组 的 方法, 好比 Sort()  方法  ,   咱们以 一维数组 为例,   为何要以 一维数组 为例,  由于 多维 数组 的 排序 说不清楚,,固然,你能够去 实现 你本身的 多维数组,包括排序 。

 

排序 中 会 对 元素 进行 比较 运算,   对于 基础类型,  好比 int  float double,  比较运算  就是   > < == != >= <=   运算符  的  运算,

可是,为了 复用代码,  Array1<T>   但愿 对于 全部 的  T 类型  都  可以 用 同一个 Sort()  方法,  这就须要 全部的 T 类型 都支持  > < == != >= <=   运算符  的  运算,

这就涉及到 运算符 重载  。

 

也能够不用 运算符 重载, 而用 C#  里的 Comparer 的 方式,   给 Sort( IComparer comparer )  方法 传一个 Comparer  对象,

可是,  对于 基础类型(int float double char  等) 来讲,  调用 Comparer 的 方法 比较大小 比起 用   > < == != >= <=   运算符   会带来一些 性能损耗 。

为了 保持 基础类型 的 原生效率,   因此 须要 运算符 重载  。

 

能够声明 这样 一个  Template  :

 

template    TCompareOperator   <T>

{

              bool     operator    =     (  T  val1,    T   val2   )     ;

              bool     operator    >     (  T  val1,    T   val2   )     ;

              bool     operator    <     (  T  val1,    T   val2   )     ;

              bool     operator    >=     (  T  val1,    T   val2   )     ;

              bool     operator    <=     (  T  val1,    T   val2   )     ;

              bool     operator    !=     (  T  val1,    T   val2   )     ;

 

而后, 把  TCompareOperator   做为 数组 的 泛型约束,

好比,   

class Array1<TCompareOperator   T>

{

              ……

              public void     Sort()

              {

                            ……

              }

}

 

可是这样的话,   Array1 的 元素 类型 只能是 实现了    TCompareOperator    的 类型  。这就有了局限,

能够把  Array1  的 名字 改为   SortableArray1   ,          哈哈哈哈    。

 

若是要让 元素 仍然 支持 全部 类型 的话,  能够 把  Sort() 方法 变成一个 静态方法,

 

public   static  void   Sort< TCompareOperator  T >( Array1<T>   arr )

 

Template  是 仅用于 值类型 的, 值类型  包括 基础类型(int float double char 等) 和 结构体,

Interface  则 用于 引用类型(对象),

 

为了 让  Sort()   方法 支持  值类型 和 引用类型 的 元素,   还须要 给 泛型约束 加上 引用类型 的 泛型约束 :

 

public   static  void   Sort< TCompareOperator   ICompareOperator     T >( Array1<T>   arr )

 

ICompareOperator   是一个 实现了   > < == != >= <=   运算符 重载  的  interface  ,

除了 interface,  基类 也能够做为 引用类型 的 泛型约束,  就是说,  基类 和 Interface 均可以 做为 引用类型 的 泛型约束  。

结构体 自己 应该也能 做为 结构体 的 泛型约束,   这 表示 这种 结构体 和 它 的 子类 结构体  能够 做为 泛型参数   。

问题是  结构体 能够继承,可是 不能 多态,   因此 要 注意 子类 结构体 实际上 被 调用 的 方法 是 哪一个 。

结构体 的 继承 纯粹 是 为了 复用代码,  子类结构体 不能 override 基类结构体 的 方法, 可是能够用 new 覆盖,

子类 结构体 的 值 和 指针 不能 赋值 给 基类 结构体 的 变量 和 指针变量  。

 

注意,   interface  这里 只做为 约束,不会 使用 多态, 由于 具体类型 会经过 泛型参数 T 指定,  因此, Sort() 方法 中 对于 T 对象 的 方法调用 仍然 是 静态编译 时 决定的 ,  和 普通 的 调用 对象方法 是 同样 的 。

除非是 A 类 实现了 I1 和 I2  两个接口, 这两个接口 有 同签名方法 Foo(),  而 Sort() 方法中 须要 调用 Foo(),

这种状况 就要看 C# 中 对于 这样的状况 会不会 报错 :

 

A a =  new  A()  ;

a.Foo()   ;   

 

A 类 对 I1  I2   两个接口 的 同签名方法 Foo()  分别进行了 显式实现,那么 在 C# 中上述   a.Foo()  代码 会不会 报错?

若是不报错,那调用的 Foo() 方法 是 哪一个 接口 的 实现 ?

 

等, 咱们在说 D# ,怎么说到 C# 了?  没错,就是说 C#,       看看 C#  怎么处理 这种状况 ,

若是 会报错,  那么 对于 D# ,   能够 先把 a 对象 转型为 要调用 Foo() 方法 的 那个 接口类型, 好比  I1  a1 =  (I1)a  ;  ,  再传给 泛型方法,

好比, 有个 方法   void Hello< I1  T >()   ,       那么 ,

 

A a =  new  A()  ;

I1  a1 =  (I1)a  ; 

Hello<I1>( a1 )   ;           //   这里将 T 声明为 I1,  若是不是这种 显式实现 多接口 同签名方法状况的话

 

看   Hello<I1>( a1 )   ;  ,   这里将 T 声明为 I1,  若是不是这种 显式实现 多接口 同签名方法状况的话,应该是

Hello<A>(a)   ;          将 T 声明为 A,

 

而 将 T 声明为 I1,    意味着   泛型方法 中 对  a.Foo() 方法 的 调用 就是 使用了 多态  。

 

多态的话,  会在 向上转型  (I1)a   时 会 查找 虚函数表,   这是 查找一个 Hash 表 ,   因此 多态 比 普通方法 调用 会多这么一个 性能损耗, 固然找到 虚函数表 后, 还要在 虚函数表 中 查找 函数指针,  这里会比 普通方法 的 动态连接 多做一个 加法,  固然这个 加法 的 性能开销 基本能够忽略  。

 

因此, 可以不使用 多态 的话 ,  就 不要 使用 多态,    固然 这是说 这里 指定 泛型参数 T 的 场合,不是说 其它 场合,  否则 的话 面向对象 爱好者 可能会 生气 的  。^^

固然, 从上面举例能够看到,  指定 泛型参数 通常状况 均可以 直接 指定 具体类型  A ,    只有 A 实现了 2 个 或 多个 interface,  这些 interface 又有 同签名 的 方法,A  对 这些 同签名 方法 显式实现 的 状况 才会须要将 A 向上转型 为 interface,   而后指定 泛型参数 T 为 interface  。

 

好吧,  其实不用这样,  就算 显式实现 多接口 同签名方法,   其实也不用 向上转型,  仍是

 

Hello<A>(a)   ;  

 

就能够 ,  咱们看 方法 定义:

 

void Hello< I1   T >()  

 

这里的  T  的 约束 是  I1 ,   编译器 能够 据此 推断出 应该调用 A 实现 I1 的 Foo()  方法  ,  因此仍然在 静态编译 时 就能够决定 调用  A 显式实现 I1 的 Foo()  方法  。

啊哈哈,  其实上面主要是 把 问题分析一下,   而且 把  D# 实现多态 的 原理 再 梳理一下  。

 

另外,还引出一个问题,  Array.Sort()  方法 

 

public   static  void   Sort< TCompareOperator  ICompareOperator  T >( Array1<T>   arr )

 

若是要对 参数 arr 像上面说的那样 向上转型 的话, 就 不是   I1 a1 =  (I1) a  ;  

而是     

Array1<A>   arr =  new   Array1<A>( 10 )  ;       //   参数 10 是 数组长度

Array1<I1>   arr1   =   ( Array1<I1> )  arr   ;

 

这就是    Array1<A> 向上转型为 Array1<I1>   的 问题 了,  至关于 C# 里的  A [ ]  转  I1 [ ],  或者  List<A>  转  List<I1>   ,

C# 里 好像 不容许 这样转,    但我想 D# 应该容许 。     ~~~

 

 

为了让 Sort() 方法 支持 基础类型 和 自定义 结构体,  基础类型 固然 会 内置 标记 为 实现了  TCompareOperator     。

虽然 标记 了 实现  TCompareOperator ,    可是 基础类型 和 自定义 结构体 运算符 重载 的 实现方式 是不同的,

基础类型 是 原生 的  C 的 运算符 运算,   而 结构体 的 运算符 重载 是一个 函数调用  。

 

说到 函数调用,会想起 内联,   别跟我说 内联,  我不太打算在 D#  里 支持 内联,

内联 是个 复杂的 东东,   是个 麻烦事,

另外,  D#  的 第一目标 是 保持 基础类型 的 原生效率,    至于 自定义 类型(包括 结构体 和 对象),  调用 函数 的 性能损耗 不算什么 。

而 现代 CPU 架构 的 寄存器 愈来愈大,     因此 函数调用  入栈出栈 传参 返回值 的 开销 应该 愈来愈 稀释 。

 

这是 运算符 重载 的 方式,  上面说了, 还能够有  IComparer 的 方式,

好比 ,  声明一个  IComparer< T>    接口 :

 

interface IComparer<T>

{

            CompareResult     Compare( T  val1 ,   T  val2 )   ;

}

enum CompareResult

{

            Greater,

            Less,

            Equal

}

 

Sort() 方法 声明为 

 

class Array1<T>

{

              public void Sort( IComparer<T>   comparer )

              {

                             ……

              }

}

 

能够看到,   IComparer<T>  的 T 是由 Array1<T>  的  T  决定的,    好比 :

Array1<int>  iArr = new Array1<int>()   ;

 

那么,  传给 Sort() 方法 的 IComparer 对象 应该是  IComparer<int>  类型  。

 

基础类型 也能够用 IComparer 的方式,  用  IComparer  方式的话,  比较 就是一次 函数调用,  对 基础类型 来讲, 效率 比 直接 用 运算符 的 方式 低一点 。

 

进一步,   还要解决一个问题,   就是  按 值 传递 和 按 指针 传递 的 结构体 在 泛型 时 调用方法 的 语法 统一 问题  。

 

好比,  咱们写一个 Hash 表,

 

class   HashTable<THash Object   TKey,     TValue>

{

           public void Add( TKey key,   TValue value )

           {

                       int  hash  =  key.GetHashCode()  ;

                       ……

           }

}

 

能够看到,   在 Add() 方法 中,   会调用   key.GetHashCode()  方法 来 计算 key 的 hash 值,

TKey 的 泛型约束 是  THash Object,   Object 是有 GetHashCode() 方法 的,  THash 也有 GetHashCode() 方法, 咱们来看看 THash 的 定义:

 

template     THash

{

             int       GetHashCode()     ;

}

 

咱们定义 2 个 结构体,  A 、B  :

 

struct      A      :     THash

{

              public    int   GetHashCode(  A   a  )

              {

                          ……

              }

}

 

struct      B      :     THash

{

              public    int   GetHashCode(  B  *   b  )

              {

                          ……

              }

}

 

A   B    都 实现了 THash 的  GetHashCode()  方法,    A  实现的是 按值传递 的 方式, B 实现的是 按指针传递 的 方式,

按值传递 和 按指针传递 可任选一种,只要 实现了一种 就算 实现了  template 的 方法 ,   固然,  2 种 都实现  也能够 。

那么,  问题来了,   对于  按指针传递  的 方式,  好比

 

HashTable < B *,    string >   ht  =  new  HashTable < B *,    string >()   ;

 

此时,   泛型生成 的 具体类型 是 :

 

class   HashTable~1

{

           public void Add( B *  key,    string value )

           {

                       int  hash  =  key.GetHashCode()  ;

                       ……

           }

}

 

能够看到,  Add() 方法 中 计算 hash 值 的 代码 是

 

int  hash  =  key.GetHashCode()  ;

 

key 是 B *  类型, 这里应该是   key ->  GetHashCode()  ;     而不是   key.GetHashCode()  ;    ,  这样就出问题了 。

 

那怎么解决这个问题?  我提出了    .->     操做符,      就是 把   . 和 ->  两个 操做符 结合到一块儿  。

.->   操做符 只能用于 泛型类 和 泛型方法 内的 代码 以及 只能用于 结构体 和 结构体指针 类型  。   当使用   .->   操做符 时,  编译器 会 判断 变量 是 结构体 仍是 结构体指针, 若是是 结构体,  则 按   .   操做符 处理,  若是是 结构体指针, 则 按   ->   操做符 处理  。

结构体指针 是指 结构体 的 一阶指针,   即  B  *   ,   只有一个 星号 的 指针  ,     若是是 二阶 或 多阶 指针,好比  B * *  ,    B * * *    ,  这种 “指针的指针”,  则 编译器 会 报错  。

 

这样就很好的实现了 对 结构体 的支持,  这很重要,  这意味着 D# 能够 支持 高性能 的 底层 代码,  这能够有效的 支持 D# 应用于   底层模块 、 基础应用 、 高性能中间件 、 游戏  。       C++  的 不少 应用场合 D# 都能 胜任  。     D#   又叫   D++    。

D#  判断 是不是  unsafe 代码 的 标准 是 是否使用了 指针,     使用了 指针 就是 unsafe,  不使用指针 就 不是 unsafe  。

D#  鼓励 使用 unsafe 代码  。                

在     底层模块 、 基础应用 、 高性能中间件 、 游戏     里 都 须要 大量使用 unsafe 代码  。      不须要 也 不提倡 使用 unsafe 代码 的 是  业务系统,  可是 业务系统 使用的 各类 基础库 内部 应该是 unsafe 代码 实现的,   好比  集合(List,   Dictionary,   Queue,   Stack,   LinkedList  等) 、  Socket  、  IO(File  等)      等等  。

还有 第三方库,    事实上 基础库 和 第三方库  没有 明显 的 界限,   上面说了,  D# 开放的 “库” 的 理念,  任何人 均可以 为   D# / ILBC   写  库,

咱们能够在 必定共识 的 基础上,  把 某些库 称为  “标准库”,   但 这也能够不是必定的,    你搞一个 “A 标准库”,  别人还能够搞一个 “B 标准库”, 还能够有 “C 标准库” 、 “D 标准库”    ……

总之 想怎么玩 均可以  。

 

咱们能够再来概括一下  结构体  的 几种 数据模型 :

1  栈 内 结构体,

2  数组 内 的 结构体, 数组 属于 堆,  数组 能够 包含 大量 海量 的 结构体,

3  数组堆

 

为了 对 第 1 种 模型 ,  栈 内 的 结构体 更好的 支持,  还能够提供 动态分配 栈 空间 的 特性 。

那 第 3 种 模型 数组堆 又是什么?    “数组堆” 这个 名字 我 是 想出来的 ,  也不必定 很贴切 。 就是 一次 申请一个 很大 的 byte 数组, 而后在 这个 数组 里 来 分配空间,   这样就不用 每次 须要 分配内存 都要 经过 堆  。

这种作法 在 C++ 里 也许叫作  “内存池”  。

 

想到 数组堆 是 由于 上面 说 基础应用 的 时候 提到 编译器,  我想 早期 的 编译器 可能 会 申请一个 比较大 的 byte 数组, 而后 把 生成的 语法成员树 保存 在 这个 byte 数组 里,   每一个 语法成员 是一个 结构体,  每次能够为 这个 结构体 在 数组 里 分配一段空间,把 结构体 保存在 这个 数组 的 一段空间 里 。

只须要有一个 变量  p  保存 剩余空间 的 首地址 就能够了,  每次分配后,  p 的 值 加上 分配的空间 大小 ,  即   p  +=   分配的 Size   ;     ,

这有点相似 栈,  p  就至关于 栈顶 ,   等 ,,,,  好像就是 栈  。

这和 堆 的 区别 和 优势 是,   数组堆 的 分配 很是快, 就是   p  +=   分配的 Size   ;       这样一个 加法 和 赋值 运算 。  并且 没有 堆 的 碎片问题, 包括 从 碎片(空闲空间) 中 寻找 最接近 分配大小 的 碎片 来 分配 的 问题,  以及 从 碎片 中 分配 后 产生 新 的 碎片 等等 复杂 的 堆 管理 问题  。

固然,相对的, 数组堆 就 没有 堆 那么 灵活  。   由于 数组堆 回收 空间 一般 时 整个 数组 所有回收,  也就是   p  =  数组首地址   ;    ,

能够 在 一个 任务 或者 请求 内 使用 数组堆,  好比 每次编译 使用一个 数组堆, 编译的结果(语法成员树) 就 存放在 这个 数组堆 里,   编译过程 中 产生的 表示 字符数组段 的 Span 结构体 也能够 放在 数组堆 里,  固然 能够 放在 另外一个 数组堆,    语法成员 和 Span  各自 使用 一个  数组堆  。   这样 每次 编译 2 个 数组堆 搞定 。

若是 涉及 到 并发,  即 多个   任务 / 请求 / 线程   之间 须要 共享数据,  那 共享数据 不适合用 数组堆, 应该 直接 使用 堆  。

大概 是 这样  。

 

不考虑 并发 的话,  就 不须要有 lock,   这样, 从 数组堆 里 分配 就是一个 加法赋值  p  +=   分配的 Size   ;   ,  清空 数组堆 就是 一个 赋值  p  =  数组首地址   ;  ,  这个速度 比起 堆 的 分配 和 回收 ,   简直  快到飞起  。

 

接下来该说什么了? 说说 Attribute,   说实在的, 真不想在 D# 里 支持 Attribute, 这会让 编译器 变得 复杂,  另外, 我是个没有耐心的人, 宏伟计划 要 作 的 事 愈来愈多 不免让人 心烦  。

Attribute  在 开发实践 中 最大的 用处 大概 是 ORM, 用于 描述 数据库   表 - 类   列 - 属性   的 对应关系 。 对于 D# 而言,  Attribute 最大的用处 是   [ DllImport( ) ]  Attribute ,  这个 用于 描述 对 外部库 函数 的 调用, 这很重要 。   

 

调用 外部库 是 ILBC / D# 的一个 核心特性 模块 基因 。

 

用 Attribute 描述  外部库 函数  确实 很方便,   比 关键字 的 描述能力 强不少,  并且 不须要 语言 增长 关键字 来支持 。

 

.Net / C#   内部 用 Attribute 倒用的很 嗨, 不少 与 开发者 无关 的  JIT / CLR 特性 都是用 Attribute 描述的 。

可是咱们再看看 Ruby 和 Javascript ,   Ruby 有 Attribute  吗?   Ruby on Rail 好像玩的很溜,     Javascript 有 Attribute 吗?   Javascript 的 动态特性 是 最吸引人的一个特性 。

 

不过总的来讲,   Attribute 好像仍是得支持一下,  毕竟 一个 完备的 元数据 体系 仍是须要的 。

支持 Attribute 也不难,   编译器 加入 对 Attribute 的 支持 也能够保持 架构 的 简洁, 这问题不大,  ILBC Runtime 对 Attribute 的 支持 也 不难, 就是个 工做量 的 问题,  由于 ILBC 原本就有 元数据系统,  这是 动态连接 和 反射 的 基础,   ILBC 动态连接 自己 就是 基于 反射 的,  反射 基于 Hash 表 查找, 固然 Hash 表 和 链表 能够共存,  就是说 成员(类 字段 方法) 同时保存在 Hash 表 和 链表 里, Hash 表 用于 按 名字 查找, 链表 用于 遍历  。

Attribute  只不过是 本来 类 字段 方法 的 元数据 上 增长一些 属性, 或者说 增长一个 属性集合  。

 

再来看看 StackTrace,  说实在的,  StackTrace 我也不想支持,  支持 StackTrace 是个 麻烦 事,  估计要作比较多的工做,虽然 提及来 两句话 就能说清楚, 好比 “在 编译 的 时候 在 每一个 方法调用 的 时候 生成一段 代码 用于 记录 StackTrace” ,  提及来简单,  但 估计 也很烦, 主要 是 咱们 没有那么多时间和精力,  这很容易分散咱们的时间和精力 。

并且,   能够看到,  StackTrace 会 在 函数调用 时 作一些 额外的工做,因此是会产生 性能损耗 的。

可是, 若是不支持 StackTrace, 会不会像 C / C++ 那样 太原始 太裸  ?

不过咱们 看看 Chrome 里的 javascript 报错时 有没有 StackTrace ?   若是没有,那 D# 也能够没有 。  ^^

 

ILBC / D# 应该是一个 能够像 脚本 同样 容易的使用 的 AOT / Native  语言平台 。   就像  Python ,  Javascript  。  固然 ILBC 是  AOT / Native  的 。

可是 从 使用上来讲,  ILBC / D# 应该能够像 脚本 同样 容易的 使用,    就像  Python ,  Javascript  。

 

固然,你能够用 ILBC / D# 开发出各类类型和 风格 的 语言,好比   AOT, JIT 脚本, 面向对象, 函数式    等等  。

 

D# 应该用 动态编译 D# 代码 取代 C# 的 Emit 和 表达式树 。

C# 的 Emit 和 Expression    是 笨重 的 堆砌  。

 

协程 是 ILBC / D# 的 一个 核心特性 模块 基因 。

有关 协程, 见《协程 的 实现方法》   http://www.javashuo.com/article/p-vojdpflm-be.html    。

 

再谈谈 调试 的 问题,     调试, 是 IDE 的 部分,  做为一个 开放 自由 有生命力 的 语言平台,  是不该该 依赖于  IDE  的,

咱们 欢迎 IDE 提供好的支持,   可是 语言平台 不该该 依赖于  IDE   。

看看  宇宙第一 IDE 和 C#  的 关系 就知道了,   离开 Visual Studio ,    怎么开发 .Net 程序?  这不可想象 。

这不只仅 是 对 C# 语法 的 语法错误 和 智能提示 的 支持,    还包括 对 具体 的 程序项目 的 支持,

好比,   WinForm 程序,     没有  Visual Studio ,你怎么写?

Asp.net 程序,   没有 Visual Studio ,   你怎么写?

并且 Visual Studio 的 WinForm ,   Asp.net  项目 拿给 VsCode 是不能直接编译的,  这是我猜的, 由于我没怎么用过 VsCode  。

这些现象,   表示 这不是 程序员 要的 “理想国”    。

 

ILBC    要实现的,是一个 用 记事本 也能写 程序 的 语言平台,    这是 程序员 的 理想国  。

这其实 很简单,  咱们只须要一些 简单 的 规则 就能够实现,  好比,  最简单的, 编译器 是一个 命令,咱们能够告诉 编译器 一个 路径, 这个 路径 就是 项目的根目录,  编译器 会 寻找 这个 目录 和 全部 的 子目录 里的 源代码 文件 进行编译,   那么 对于 Bin 目录, 或者 资源目录 等等一些 咱们 须要 编译器 忽略 的 目录 怎么办?

能够相似 Git,   在  项目目录 下 放一个  ilbc_src.ignore   的 文件,   里面声明 须要 忽略 的 目录,  就能够了  。

 

甚至,  能够比 Git  还简单,   ilbc_src.ignore   只容许 声明 项目目录 下 的 第一级 子目录,  这样 就 太简单了  。

实际上,   这也够用了  。

 

编译器 对 项目目录 下的 源文件 编译,  会把 全部的错误 都在 控制台 里 列出来,  哪一个位置是什么错, 这和 Visual Studio 或者 其它 IDE 是同样的 。

 

对于 像   WPF,  Asp.net    这种类型 的 项目,  有 Xml 格式 的 前端代码(文件),   这也没问题,   你能够用 Xml 编辑器 来写 前端代码(文件),  固然, 用 记事本 也能够  。  ^^

而后,   编译器 一样 对 项目目录 下 全部的 源代码文件, 包括 前端文件 和 后端文件 进行编译 ,   并显示 全部错误  。

 

因此,   无论 后端代码 仍是 前端代码 ,   你能够选择 任意的 文本编辑器 来 编写,  而后 交给 编译器 来编译  。

你也能够 根据上述 规则 开发一个   IDE  ,     这均可以  。

 

你的项目 拿给 别人,     别人 能够用 本身的 文本编辑器 和 编译器 来 继续 开发 和 编译,也能够用 IDE  。

 

在这方面,    Chrome   彷佛 干的不错,    你能够用 任意 的 文本编辑器 写 Javascript,   而后 Chrome 的 DevTools 可让你很方便 的 调试程序 。

相比之下,     Visual Studio  在 不一样 版本间  都 可能 不支持 互相 打开项目  。

 

等,  咱们是说 调试 的,  怎么说着说着 就 跑题了 ?      调试 是一个 麻烦事,   原理 大概 是这样:

首先,要 调用一个 操做系统 提供的 API  “附加进程”,   调用了这个 API 就能够 把 系统的 调试器 附加 到 要调试的 进程,  以后 进程 每执行一条指令 大概都会发起一个中断,  系统会在这个中断里 调用 咱们的 处理程序,   咱们的 处理程序 能够知道 当前是 在 执行 第几行 的 反汇编代码,   根据 汇编代码 的 行数 和 指令 去 “符号表” 中 寻找 这一行 对应的 源代码 是 哪一行,  而且 指令 也要 和 符号表 中 对应的 源代码行 的 指令 符合,   这样就能够 单步调试 了  。

符号表 就是 好比 .Net 里的 pdb 文件,   在 编译时 会 生成 汇编代码 和 源代码 的 对照表, 这个 对照表 就是 符号表 。

固然,以上 这些 是 我 猜 的 。 

 

这些 实际上 作起来 很麻烦,    并且 这些是 最基本,  通常 调试 除了  单步调试,  还有 Step Over Function,    Step Out Function,    直接运行到下一个断点,  还有 查看 局部变量 全局变量 。

 

因此, 我建议,  没必要将 精力 放在 实现 调试 上, 可是, 应该提供 控制台 输出 的 支持 。

控制台输出  是  最古老 的 调试方式, 也是  永不过期  的 调试方法  。

 

数组堆

 

template interface

值类型 结构体 结构体 方法, 传值, 传指针, 泛型, 数组 ,集合类泛型,  .->  操做符,  > < >= <=  == 运算符重载, Comparer,

栈 结构体, 对象图,

底层应用,基础应用,    图形图像,2D 3D,游戏,  视频音频, 视频采样,音频采样,人工智能,大数据,数据库,文档数据库,大规模并行计算文档数据库,搜索引擎,爬虫,编译器  , 文本编辑器,  大型文本编辑器    。

Attribute,   

调用 外部库 是 ILBC / D# 的一个 核心特性 模块 基因 。

StackTrace,

ILBC / D# 应该是一个 能够像 脚本 同样 容易的使用 的 AOT / Native  语言平台 。

固然,你能够用 ILBC / D# 开发出各类类型和 风格 的 语言,好比 AOT JIT 脚本,面向对象,函数式 等等 。

 

D# 用 动态编译 D# 代码 取代 C# 的 Emit 和 表达式树 。

 

调试

 

ILBC    源代码 项目 规范 

 

协程 是 ILBC / D# 的 一个 核心特性 模块 基因 。

 

template 和 interface   同时做为一等公民

D# 是程序员的语言,不须要太多包装和修饰,

D# 是 简单的, 编译器 和 IDE 都是,    程序员 的 语言 是 简单的

 

let  , 相似 js 的 let,     用以 支持 完备 的 闭包 特性,通常 的 变量 至关于 js 的 var ,  加上 let 至关于 js 的 let,好比 let string  s  ;

能够起到 在 循环 中 给 闭包 传递 变量 的 做用,

 

Task 库

 

返回多个 返回值,好比 D# 中

 

public (int i,  string s)  Foo()

{

        int i;

        string s;

        ……           //  对  i, s  操做

 

        return  (i, s);

}

 

var r = Foo();

r.i   ……       //  r.i  表示返回的  i

r.s  ……       //  r.s 表示返回的  s

 

编译为 InnerC 是这样:

 

struct R<>1

{

         int  i

         string  s,

}

 

Foo()

{

         R<>1   r  ;

         r.i     ……       // 对  i  操做

         r.s    ……       // 对  s  操做

         ……

         return   r  ;

}

 

R<>1  r  =  Foo()  ;

r.i   ……       //  r.i  表示返回的  i

r.s  ……       //  r.s 表示返回的  s

 

D#  是否 要 支持  async await 语法?

我曾经想过用    闭包 + 观察者 来 取代   async + 职责链,   但 后来 发现,   闭包 和 职责链  好像 更配,这 有点讽刺 ……

但 其实 闭包 + 观察者  也是 能够的, 好比 Cef 中  ResourceHandler  的 方法里 能够    return true / false ;   和   callback.Continue()   ;

若是 当前 就处理完了 任务,  则 能够直接 return true / false,   告知 Cef  能够进行下一个任务,

若是 当前 任务 异步 执行,   则 能够 等 异步任务 完成时 调用   callback.Continue()   告知 Cef  能够进行下一个任务 。

 

今天看到 群 里 有 网友介绍 kooboo json  使用 表达式树 提升效率 ,  https://github.com/Kooboo/Json/blob/master/Kooboo.Json/Formatter/Deserializer/Expression/CollectionBuild.cs   

这样固然很好,  可是我仍是以为 表达式树 太复杂 了,我以为应该写 C# 代码 来 动态编译 就能够  。

好比

 

“ obj.name = dataRead [ \“ name \” ] ; ”

 

这是 ORM 里 给 Entity 属性 赋值 的 代码,   这能够算是  C#  脚本  。


这种作法 只是 第一次 编译时 可能 比 表达式树 多一点时间 ,  编译完成后 运行效率 是同样的 。

 

其实 第一次 编译时 也多不了 多少时间,   只是 多了 文本语法分析 的 时间 。

 

之后  D# 也要这么干, 提供 动态编译,支持  D# 脚本 。

D# 是 不会 提供 C# 那样的 表达式树 的 。

 

C# 的 表达式树 就是 面向对象 泛滥 的 结果,   认为何均可以用 对象 来表达,

其实 字符串 表达的会更好 。

 

在 D# 看来,  C# 的 Emit 和 Expression    是没有意义的堆砌  。

在 D# 脚本 看来,  C# 的 Emit 和 Expression    是 不须要 的   。

相关文章
相关标签/搜索