Ryuu 的个人博客

一个计算机初学者

隐式类型的局部变量是为了支持匿名类型而加入C#语言的。

用 var 来声明变量而不指明其类型,可以令开发者把注意力更多的集中在名称上,从而更好的了解其含义。

在很多情况下,完全可以使用 var 来声明隐式类型的局部变量,因为编译器会自动选择合适的类型。但是不能滥用这种方法,这样可能令代码难以阅读,甚至产生微妙的类型转换 BUG。

局部变量的类型推断机制并不影响 C# 的静态类型检查。类型推断不等于动态类型检查。用 var 来声明的变量是强类型的,他的类型由赋值符号右侧值的类型确定。var 的意义在于,你不用把变量的类型告诉编译器,编译器会替你判断。

如下情况变量类型是显而易见的 (构造函数与工厂方法

1
2
var foo = new MyType();
var thing = AccountFactory.CreateSavingAccount();

如下情况变量类型没有清晰的指出

1
var result = someObject.DoSomeWork(anotherParameter);

在这种情况下应当在变量声明时指定清晰的名称,尽管方法并没有指出返回类型,但这样就能让开发者推断出变量类型,例如:

1
var HighestSellingProduct = someObject.DoSomeWork(anotherParameter);

查看代码的人会根据自己的理解认定该变量类型,可能恰好与变量在运行期的真实类型相符。但编译器不会像人那样思考,而是根据声明判定其在编译期的类型。若用 var 进行声明,编译器会推断其类型,而开发者看不到编译器推断的类型。因此,他们所认定的类型可能不符,这会在代码的维护中导致错误的修改,并产生一些本来可以避免的 Bug。

如果隐式变量的局部类型是 C# 的数值类型,那么还会产生一些另外的问题,因为在使用这些数值的时候可能会进行各种形式的转换。有些转换是宽转换 (widening conversion),这种转换是安全的,例如从 float 到 double,但还有一些转换是窄转换 (narrowing conversion),这种转化会使精度下降,例如从 long 到 int。如果明确的指出数值变量的类型,那么可以更好的控制,并且编辑器也有可能将窄转换标记出来。

总之,除非开发者必须看到变量的声明类型之后才能正确的理解代码的含义,否则优先考虑var来声局部变量(此处的开发者当然也包括编写代码的人本身,因为可能有查看早前写过的代码的需求)。注意上文的优先而不是总是,例如上文的转换错误问题,对int, float, double等数值型的变量,就应该指出明确类型。

C# 有两种常量,一种是**编译期 (compile-time)的,另一种是运行期 (runtime)**的。

1
2
3
4
5
// Compile-time constant:
public const int Millennium = 2000;

// Runtime constant:
public static readonly int ThisYear = 2004;

以上代码展示了如何在 class 或 struct 的范围内声明这两种常量 此外编译期常量还能在方法中声明,而 readyonly 常量则不行。

编译器的常量取值嵌入目标代码中。例如

1
if(myDateTime.Year == Millennium)

编译成 IL 之后,与直接使用字面量2000是一样的

1
if(myDateTime.Year == 2000)

运行期常量与之不同,如果代码中使用到了运行期常量,那么其生成的 IL 的、也会同样引用该变量,而不会直接使用字面量。

这两种变量支持的值也不一样。编译期的常量只能用来表示内置的 int, float, enum, string。非基本变量不能使用编译期常量声明,需要使用 readonly。在生成 IL 的过程中,只有用来表示这些原始类型的编译期常量才会替换成字面量。

无法编译,试图使用 new 操作符进行初始化: (Ryuu: 即使是参数是值类型也不行,其对象在编译期不存在)

1
2
3
// DON'T DO THIS!
// Does not compile ,use readonly instead:
private const DateTime classCreation = new DateTime(2000, 1, 1, 0, 0, 0);

编译期常量只能用数字,字符串或 null 初始化。readonly 常量在执行完构造函数 (constructor) 之后不可以再修改。(和编译期常量不同,他的值是在执行完构造函数后才初始化的)

在生成 IL 的时候,代码中的编译期常量会直接以其常量值写入,如果在制作另外的程序集 (assembly) 的时候用到了该程序集中的编译期常量,那么这个常量将会以字面值写入另外的程序集。

有的时候开发者确实想把某个值固定在编译期,比如程序版本记录,如果更新整个项目,那么里面每个版本号都会变为最新,如果仅更新其中某些程序集,那么只有更新的程序集的版本号会变为最新值。

const 的性能比 readonly 的要好。由于程序集可以直接访问值,而不用查询变量,因此性能稍高。但是,开发者需要考虑是否值得为了这点性能而使得代码变得僵硬。在决定这么做之前,您应该先通过 profile 工具做性能测试。(可以试试 BenchmarkDotNet)

const 关键字用来声明那些必须在编译期得以确定的值,例如 attribute 参数、switch case 语句的标签、enum 的定义等,偶尔用于声明不会随版本而变化的值。除此之外的值考虑用 readonly 常量声明。

静态工厂和构造器有个共同的局限性:都不能很好地扩展大量的可选参数

例:

考虑用一个类表示包装食品外面显示的营养成分标签。这些标签中有几个域是必需的:每份含量,每罐含量,每份卡路里,还有超过20个可选域:总脂肪量,饱和脂肪量,转化脂肪量,胆固醇,钠等等。大多数的产品在某几个可选域中都会有非0的值。

对于这样的类,应该用哪种构造器或者静态方法来编写呢?

1. 重叠构造器 (telescoping constructor)

提供第一个只有必要参数的构造器

提供第二个包含必要参数且包含一个可选参数的构造器

提供第三个包含必要参数且包含两个可选参数的构造器

以此类推,最后一个构造器包含所有参数

如下是个简单的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class NutritionFacts {
private final int servingSize; //(ml) required
private final int servings; //(per container) required
private final int calories; // optional
private final int fat; //(g) optional
private final int sodium; //(mg) optional
private final int carbohydrate; //(g) optional

//必须的选项
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0);
}

public NutritionFacts(int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 0);
}

public NutritionFacts(int servingSize, int servings, int calories, int fat) {
this(servingSize, servings, calories, fat, 0);
}

public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}

//包含所有的选项
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
}

仅仅想创建一个该类对象,使用最短的构造器即可

但如果想要设置参数表中靠后的参数问题就来了

1
NutritionFacts cocaCola = new NutritionFacts(240,8,100,0,35,27);

如上初始化中 fat 的值为0,这个参数本是不用初始化的,就6个参数的情况下,还说的过去,随着参数增加,这样就不行了。

重叠构造器是可行的,但当参数增多时,客户端的代码将变得很难编写,阅读性也较差,并且如果初始化时容易填错顺序,导致运行时的错误。

2. JavaBeans模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// JavaBeans Pattern - allows inconsistency, mandates mutability
public class NutritionFacts {
private int servingSize = -1; //(ml) required
private int servings = -1; //(per container) required
private int calories = 0; // optional
private int fat = 0; //(g) optional
private int sodium = 0; //(mg) optional
private int carbohydrate = 0; //(g) optional

public NutritionFacts() {
}

public void setServingSize(int servingSize) {
this.servingSize = servingSize;
}

public void setServings(int servings) {
this.servings = servings;
}

public void setCalories(int calories) {
this.calories = calories;
}

public void setFat(int fat) {
this.fat = fat;
}

public void setSodium(int sodium) {
this.sodium = sodium;
}

public void setCarbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
}
}

// 实例化
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);

JavaBeans模式创建实例很容易,代码可读性也很强,但其有很严重的缺点:

  1. 构造的过程包含多个调用,在构造过程中JavaBeans可能处于不一致状态

    例如一个线程正在使用setter初始化值,而另一个线程正用getter取得该对象的字段

  2. 使用JavaBeans模式则该类不可成为不可变类 (因为有setter访问器)

3. Builder模式

此模式有重叠构造器的安全性,也有JavaBeans模式的高可读性

不直接生成需要的对象,而是得到一个builder对象,调用所有的必要的构造器或静态工厂,设置每一个需要设置的参数,最后调用一个无参数的build方法来生成一个不可变对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;

public NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}

public static class Builder {
// Required parameters
private int servingSize;
private int servings;
private int calories;
private int fat;
private int sodium;
private int carbohydrate;

public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}

// builder的 setter 返回自身实现链接调用
public Builder calories(int calories) {
this.calories = calories;
return this;
}

public Builder fat(int fat) {
this.fat = fat;
return this;
}

public Builder sodium(int sodium) {
this.sodium = sodium;
return this;
}

public Builder carbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
return this;
}

public NutritionFacts build() {
return new NutritionFacts(this);
}
}
}

// 实例化
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();

Builder 模式的可读性提高了很多,代码简易。Builder模式模拟了了具名可选参数

如果该类包含多个字段,Builder模式就是种不错的选择,特别是当大多数参数都是可选的时候。与重叠构造器与JavaBeans相比要更加安全与易读。

静态工厂方法与构造器不同的优点

  1. 它们有名称 (静态方法)

    例:构造器 BigInteger(int, int, Random) 返回的 BigInteger 可能为素数,如果用名为 BigInteger.probablePrime 的静态工厂方法来表示,显然更为清楚 (1.4的发行版本中最终增加了这个方法)

  2. 不必每次调用时都创建一个新对象 (可以将构建好的实例进行缓存并重复利用)

    Boolean.valueOf(boolean) 从来不创建对象。这种方法类似于 Flyweight模式(享元模式)。如果程序经常创建相同的对象,并且创建对象的代价高,这种技术将会极大的提升性能。

  3. 可以返回原返回类型的任何子类型对象 (可以返回隐藏类的实例,将实现类隐藏将使得API十分简洁)

  4. 创建参数化类型(泛型)实例的时候,使代码变得更加简洁 (类型推导 type inference)

    1
    2
    3
    4
    5
    6
    7
    8
    // 调用参数化构造器时,指明类型
    Map<String, List<String>> map = new HashMap<String, List<String>>();
    // 假如在 HashMap 中提供静态工厂
    public static <K, V> HashMap<K, V> newInstance() {
    return new HashMap<K, V>();
    }
    // 创建实例将会变简洁
    Map<String, List<String>> map = HashMap.newInstance();

静态工厂方法的缺点

  1. 类如果不含公有的或者受保护的构造器,就不能被子类化

  2. 与其他的静态方法实际上没有任何区别

    在API文档中,静态工厂方法不会像构造器一样被单独列出并标识,对于仅提供静态工厂方法的类而言,想查到如何实例化该类是比较麻烦的。

    静态工厂方法的一些惯用名称如下:

    1. valueOf
    2. of
    3. getInstance
    4. newInstance
    5. getType
    6. newType

静态工厂方法和共有构造器各有用处,一般情况下静态工厂更加合适,优先提供静态工厂,而不是共有构造器

个人总结: 静态工厂方法是对获取对象的一种封装(封装为静态方法),具体实现可隐藏于其中,具有更高的灵活性

类型参数的约束指出了能完成该泛型类工作的类必须具有的行为。若是某一类型无法满足约束,那么自然无法用于该泛型类型中。不过这也意味着,每次在泛型类型中引入新的的束,都会给该类型的使用者增加更多的工作。实际情况各不相同,因此并没有万能的解决方案,不过太过极端总归是不好的。若是不给出任何约束,那么则必须在运行时进行过多检查,比如使用强制转换。反射并抛出运行时异常等来保证程序的正确性。而若是约束过多,那么也会让类的使用者觉得麻烦。因此你要找到那个恰到好处的中间点,精确地时类型参数给出约束,不多也不少。
约束能让编译器了解某个类型参数更具体的信息,而不仅限于极为笼统的System.Object。在创建泛型类型时,C# 编译器将要为泛型类型的定义生成合法的IL。而在进行编译时,虽然编译器对今后可能用来替换类型参数的具体类型了解甚少,但你需要生成合法的程序集。若是不添加任何约束,那么编译器只能假设这些类型仅具有最基本的特性,即System.Object中定义的方法。
​ 编译器无法猜测出你对类型的假设,唯一能够确认的就是该类型继承于System.Object(因此我们无法创建不安全的泛型,也无法将指针作为类型参数。)我们知道。System.Object的功能非常有限,因此若是使用到了任何非System.Object的功能,编译器均会抛出异常。你甚至都无法使用最基础的构造函数new T(),因为若某个类型仅提供了有参数的构造函数,那么该构造函数将会被隐藏。

最小化约束方法有很多种.其中最常见的一种是,确保泛型类型不要求其不需要的功能

以IEquatable为例,这个是个很常用的接口,且创建新类型时也经常用到.

我们可以重写AreEqual方法,让其调用Equals方法.

1
2
3
4
public static AreEqual<T>(T left, T right)
{
return left.Equals(right);
}

在上述代码中,若AreEqual()定义在一个带有IEquatable约束的泛型类中,那么AreEqual将调用IEquatable.Equals.否则, C#编译器则不会假设具体类型一定会实现IEquatable,因此唯一可用的Equals()就是System.Object.Equals().

上述示例可以看到C#泛型和C++模板之间的主要区别.

在C#中,编译器仅能使用约束给出的信息来生成IL. 即使为某个特定的实例指定的类型拥有更好的方法,也不会在运行时使用,除非在该泛型类型编译时就给出限定.

.NET 平台的头两个版本 (1.1 及 1.2) 不支持泛型

System.Object 是所有类型的最终基类

为何要使用泛型代码?

  1. 健壮性

将 Object 作为 参数或返回类型 难免会出现意外的类型 导致运行时的错误

  1. 性能

    1.1 版本的弱类型系统需要在代码中添加检验代码以保证参数或返回类型的正确

    ,当检验失败时还会执行更多的其他代码,这不可避免的导致了更大的性能开销

“总体说来,弱类型系统将带来各种各样的麻烦,从性能低下直至程序异常终止等。”

自 .NET 2.0 引入了泛型

以 System.IComparable 为例比较泛型版本的优势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface IComparable<T>
{
int CompareTo(T other);
}

// 1.1版本
public interface IComparable
{
int CompareTo(object obj);
}

// 实现
public int CompareTo(Customer right)
{
return Name.CompareTo(right.Name);
}

public int CompareTo(object right)
{
if (!(right is Customer))
throw new ArgumentException("Argument not a customer","right");
Customer rightCustomer = (Customer)right;
return Name.CompareTo(rightCustomer.Name);
}

使用泛型接口的四个优势

  1. 简洁
  2. 高效
  3. 避免了所有的装箱/拆箱以及类型转换操作 (个人认为这点其实包含在1与2中)
  4. 不会抛出异常 (非泛型版本中运行时可能出现的异常变为可由编辑器捕获的异常

个人总结: 用基类传来传去固然是不怎么安全,并且还会增加代码量与更多性能开销,一般情况下用泛型约束确实是最好的选择 (参数不继承自同一基类另说)

在当前网页按下 F12,转至 Console 输入并回车:

1
2
3
4
// 设置文档body元素内容可编辑性为 true
javascript:document.body.contentEditable='true'
// 打开文档设计模式
document.designMode='on'

便可操作网页中的文本 (如删除,剪切,粘贴,输入操作)

Hexo 小姿势

在本地启动 hexo server

(ctrl + c 停止)

1
hexo s

创建新的博客文章 new blog

1
hexo n "文章名称"

生成文件并部署至仓库 deploy

1
hexo d

部署的信息在 _config.yml 文件最下的 deploy

// _config.yml 示例 (记得冒号后要有空格)

1
2
3
4
5
6
# Deployment
## Docs: https://hexo.io/docs/deployment.html
deploy:
type: git
repo: https://github.com/Ryuu-64/Ryuu-64.github.io
branch: master

hexo 小问题

当node 版本过高时会产生以下 Warning

1
2
3
4
5
6
7
8
9
10
11
12
13
(node:13888) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)
(node:13888) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:13888) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
(node:13888) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency
(node:13888) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:13888) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
(node:10816) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:10816) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
(node:10816) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency
(node:10816) Warning: Accessing non-existent property 'column' of module exports inside circular dependency
(node:10816) Warning: Accessing non-existent property 'filename' of module exports inside circular dependency
(node:10816) Warning: Accessing non-existent property 'lineno' of module exports inside circular dependency

官方的 issues https://github.com/stylus/stylus/issues/2534

解决方案

不用改变node的版本

找到项目中的此文件: \node_modules\stylus\lib\nodes\index.js 在最前处添加如下代码即可

1
2
3
exports.lineno = null;
exports.column = null;
exports.filename = null;

后续问题

结果还是有问题

博客部署出错了 详情如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FATAL Something's wrong. Maybe you can find the solution here: https://hexo.io/docs/troubleshooting.html
TypeError [ERR_INVALID_ARG_TYPE]: The "mode" argument must be integer. Received an instance of Object
at copyFile (fs.js:1972:10)
at tryCatcher (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\util.js:16:23)
at ret (eval at makeNodePromisifiedEval (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promisify.js:184:12), <anonymous>:13:39)
at F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\hexo-fs\lib\fs.js:144:39
at tryCatcher (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\util.js:16:23)
at Promise._settlePromiseFromHandler (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:547:31)
at Promise._settlePromise (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:604:18)
at Promise._settlePromise0 (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:649:10)
at Promise._settlePromises (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:729:18)
at Promise._fulfill (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:673:18)
at Promise._resolveCallback (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:466:57)
at Promise._settlePromiseFromHandler (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:559:17)
at Promise._settlePromise (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:604:18)
at Promise._settlePromise0 (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:649:10)
at Promise._settlePromises (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:729:18)
at Promise._fulfill (F:\Users\Ryuu\Documents\GitWork\MyBlog\HexoBlogLib\node_modules\bluebird\js\release\promise.js:673:18)

最终方案

下载一个 nodejs 的 12.x 的版本

再部署就没有任何问题了

还有问题就把高版本的 nodejs 删除

我的博客搭建详细步骤

事前准备

1.安装 Node.js

Node.js® 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境
建议下载 LTS 版本 (Long Term Support | 长期支持)
官网上下载安装包,然后跟着提示进行操作即可
下载完毕后可以进入控制台
输入 node -v 查看 node 版本
输入 npm -v 查看 npm 版本 (node package manager | node包管理器)

2.安装 git

Git 是一个免费的开源分布式版本控制系统
官网上下载安装包,然后跟着提示进行操作即可,步骤较多,可参考各种安装教程
下载完毕后可以进入控制台,输入 git –version 查看 git 版本,确认安装成功

git config –global user.name “输入你的名称”
git config –global user.email “输入你的邮箱地址”
设置自己的名称和电子邮箱

3.安装淘宝镜像 (可选步骤)

如果使用 npm 安装依赖过慢
进入控制台输入如下命令安装淘宝镜像
npm install -g cnpm –registry=https://registry.npm.taobao.org
使用 cnpm 代替 npm 以提升速度

开始搭建

阅读全文 »
0%