题记:这是自己的观后感,工作两年了,本来打算好好学习设计模式,或者作为客户端深入了解GPU编程的,但是突然发现还有这么一本书。

《编写高质量代码改善建议》,感觉这正是自己需要的。

我是做游戏开发的,最近一段时间工作,接触到了数学编程,涉及到大量的计算,策划那边增改需求也很多,加上我的项目对性能要求很高。微量的计算影响到

性能。所以对代码质量要求很高,明显自己的代码质量已经不达标了。所以,我还是打牢固基础,编写高质量代码才是王道。

之前工作经历了很多其他人的代码,什么工作三四年,甚至四五年的代码都是惨不忍睹,我都是“瞧不起”的。

自己不想成为“自己眼中的他们”。这本书,大部分自己都是会的,但是总结的相当到位。毕竟

也有两年经验了,打算用3个月时间,看完,高质量的写完读后感,当然其中也会加入自己的通俗易懂的看法,取其精华。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

1.正确操作字符串。

1)尽量避免少的装箱。

string str1 = "djw" + 9;
string str2 = "djw" + 9.ToString(); (高效)

第一行代码会发生一次装箱操作。

第二行代码没有,因为调用的是整型的ToString方法。

(整型ToString()是直接通过操作内存来完成int到string的转换,效率比装箱高很多。)

补充说明:

大家都知道无论值类型还是引用类型其本质上都是object类型,即引用类型。

装箱:将值类型转为引用类型(object)。(通俗来说,将一个单一的东西转为万能的,当然慢)

拆箱:引用类型转为值类型。(将万能转为单一东西,当然快)

注:装箱之所以会带来性能消耗,因为需要完成下面三个步骤。

  I.首先会为值类型在托管堆中分配内存。

  II.将值类型的值复制到新分配的堆内存总。

  III.返回已经成为引用类型的对象的地址。

2)避免分配额外的内存空间。

string是个很特殊的对象,一旦赋值不可改变。运行时调用System.String类中任何对象进行运算, = , + 等,都会在内存中

创建新的字符串对象,这意味着为该对象分配新的内存空间。

运行时额外开销:

 private static void Method1()
{
string s1 = "aaa";
s1 = "" + s1 + "";
//创建了3个字符串对象,(开辟三个内存空间),并调用一次string.Contract
} private static void Method2()
{
string s1 = + "";
//发生一次装箱操作,调用一次string.Contract方法
} private static void Method3()
{
string s1 = "ss" + "dd" + "cc";
//相当于 s1 = "ssddcc";
} private static void Method4()
{
const string a = "t"; //因为a 是一个常量
string s1 = "abs" + a;
//该代码等效于 s1 = "abs" +"t"
//等效于 s1 = "abst";
}

对于操作频繁的明显带来性能消耗,可以用StringBuilder来弥补。可以参考我这篇帖子:http://www.cnblogs.com/u3ddjw/p/6823346.html

string.format内部方法使用StringBuilder进行字符串公式化。

2.as is 区别

as  :不能操作基元类型,永远不会抛出异常,如果类型不匹配则返回null

is    :包含基元类型,但是返回true/false

强制转换 (People)SomeObbject :可能引发异常,故效率不如as了

总结 即使可以用强制类型转换,但是出于效率的角度,建议用as

3.区别const 和 readonly的使用方法

const,应用上更加效率。

编译期常量,因为如此,所以天然就是static,不能再前面增加static关键字修饰。

之所以const效率高,是因为经过编译器编译之后,我们在代码中引用const的地方会用const变量所对应的实际值来代替。

只能修饰基元类型,枚举类型或字符串类型。

readonly,大部分应用情况,“效率”并没有那么高的地位,我们更愿意采用readonly,因为readonly赋予代码更多的灵活性。

运行时常量。第一次被赋值后将不可改变。(这句话,不完全正确,再加上"除了在构造函数中,可以被赋值多次”)

修饰没有限制)

readonly 属于类实例的成员,要使他成为类的成员,需要在前面加上static,这样就可以直接使用类名调用.

(构造函数,变量初始化 都可以赋值)

4.枚举的正确使用

  1)将0 作为枚举的默认值。

  2)  避免给枚举类型的元素提供显示的值。

    因为如果没有给元素显示的赋值,编译器会逐个为元素的值 + 1.

5.习惯使用重载运算符

   开发过程中应该习惯使用微软提供给我们的语法特性。 

 class Salary
{
public int RMB;
public static Salary operator +(Salary a,Salary b)
{
b.RMB += a.RMB;
return b;
}
}

6.创建对象时考虑是否实现比较器

  编码中,一般我们实现.Net自带的类,方法比较多,但是很多时候,接口,也给我们提供很多方便的功能。我们用的比较少(可能我学的还是比较浅吧)。

比较器的使用,真的很方便,我直接上可使用的代码。

  IComparable :接口,类直接可继承。

  IComparer :接口,自定义类型继承。

  CompareTo:方法,简写比较方法。

 public class Salary:IComparable<Salary>
{
public int BaseSalary { get; set; }
public int Bonus { get; set; } public int CompareTo(Salary sa)
{
return BaseSalary.CompareTo(sa.BaseSalary);
} public Salary(int baseSalary,int bonus)
{
this.BaseSalary = baseSalary;
this.Bonus = bonus;
}
}   //自定义排序(可根据该,进行扩展,实现你需要的类型比较,是不是超方便)
public class SortSalaryBonus:IComparer<Salary>
{
public int Compare(Salary sa,Salary sb)
{
return sa.Bonus.CompareTo(sb.Bonus);
}
} static void Main(string[] args)
{
List<Salary> salarys = new List<Salary>();
salarys.Add(new Salary(, ));
salarys.Add(new Salary(, ));
salarys.Add(new Salary(, ));
salarys.Add(new Salary(, ));
salarys.Sort();
for (int i = ; i < salarys.Count; i++)
{
Console.WriteLine(salarys[i].BaseSalary);
}
salarys.Sort(new SortSalaryBonus());
for (int i = ; i < salarys.Count;i++ )
{
Console.WriteLine(salarys[i].Bonus);
}
Console.Read();
}

7.区别对待 == 和 Equals

1)Equals

首先,要明确“相等性”的概念。CLR中将“相等性”分为两类,“值相等”和“引用相等”。

如果用来比较的两个变量所包含的数值相等,那么将其定义为“值相等”;

如果比较的两个变量引用的是同一内存,那么将其定义为“引用相等”。

但是无论 == 还是 equals,都一个原则:0

对于值类型,如果类型的值相等,就返回true。

对于引用类型,如果类型指向同一个对象,则返回true。

同时我们需要了解,无论是操作符“==”还是“Equals”方法都是可以被重载的。比如string这样一个特殊的引用类型,

微软觉得它的实际意义更接近值类型,所以在FCL中,string的比较被重在为针对“类型的值”,而不是针对“引用本身”

的比较。从设计角度,很多自定义的类型(尤其是自定义的引用类型)会存在和string比较接近的情况,这时候需要重载

Equals这个方法。

但是这里Equals有什么跟==的区别?

我们写一个简单的Person类。

   

  public class Person
{
public string IdCode;
public Person(string id)
{
IdCode = id;
}
} Person p1 = new Person("");
Person p2 = new Person("");
 public override bool Equals(object obj)
{
return IdCode == (obj as Person).IdCode;
}

此时  p1 == p2 p1.Equals(p2)             false,true

(我这边理解的Equals 和 ==,Equals就是给你用于引用类型重载出你想要的 相等性,而==用于值类型的值保持其本来的相等性 !!!!!!真贴心啊)

2)GetHashCode

看到上图后注意:GetHashCode不一样,GetHashCode是干嘛的。

Object为所有的CLR类型提供了GetHashCode的默认实现。每New一个对象,CLR都会为该对象生成一个固定的整型值,

该整型值在对象的生命周期内不会改变,而对象默认的GetHashCode实现就是对该整型值求HashCode。

还有就是,虽然我们重写Equals后比较是相等的,但是如果我们存到键值的集合中,以该Person作为key,再取,就不是相等的。

因为由于CLR内部会优化这种查找,实际上是根据HashCode来查找Value值。代码运行的时候首先会调用Person类型的GetHashCode·

由于发现Person没有实现GetHashCode,所以CLR最终会调用Object的GetHashCode的方法。所以导致这样的相等的Person查找,是找不到的。

public override bool Equals(object obj)
        {
            return IdCode == (obj as Person).IdCode;
        }

GetHashCode方法哎存在另外一个问题,他永远只返回一个整型类型,而整型类型的容量显然无法满足字符串的容量。

例如:

    string s1 = "2222222222sada";

string s2 = "121122222222222";

    s1.GetHashCode() ;s2.GetHashCode();

为了减少两个不同类型之间根据字符串产生相同的HashCode的概率,

改进:  

 public override int GetHashCode()
{
//不懂为啥这么写,但是知道这么写就行了,暂时没必要深究了。
return (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "#" + this.IdCode).GetHashCode();
// return this.IdCode.GetHashCode();
}

注意:也要记得实现IEquatable<T>.

  

 public bool Equals(Person other)
{
return this.IdCode == other.IdCode;
}

8.正确实现浅拷贝和深拷贝

1)浅拷贝:

    (一般情况)将对象中的所有字段复制到新的对象(副本)中。其中,值类型字段的值被复制到副本后,

在副本中的修改不会影响到源对象对应的值。而引用类型的字段被复制到副本中的是引用类型的引用,

而不是引用的对象,在副本中对引用类型的字段值做修改会影响到源对象本身。

    (string 的拷贝,理论上string 是引用类型,但是由于该引用类型的特殊性,Object.MemberwiseClone

的方法依旧为其创建了副本。也就是拷贝过程,应该将string类型看成值类型)

2)深拷贝:

    将对象中的所有字段复制到新的对象中,无论是对象的值类型字段,还是引用类型的字段,

都会被重新创建并赋值,对于副本的修改,不会影响到源对象本身。

    实现方式:a.反射 b.序列化 c.表达式树 d 慢慢手动赋值。反射进行深拷贝存在问题较多,表达式树又比较复杂,

手动慢慢赋值如果类型字段发生变化或增减,那么拷贝方法也要相应变化。

我这里只讲述 序列化进行深拷贝。

无论是深,还是浅拷贝,微软都建议用  实现 ICloneable接口的方式明确告诉调用者,该类型可以被拷贝。

当然,ICloneable接口只提供了一个声明为Clone的方法,我们可以根据需求Clone方法实现浅拷贝或深拷贝。

序列化实现方式(序列化方法,看一遍就好,了解一下就行了)

[Serializable]
class Employee:ICloneable
{
public string IDCode { get; set; }
public int Age;
public Department department;
public Object Clone()
{
return this.MemberwiseClone();
} /// <summary>
/// 深拷贝,至于里面为啥那么写,我也就没深入研究了,大概知道就好了。
/// </summary>
/// <returns></returns>
public Employee DeepClone()
{
using (Stream objectStream =new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(objectStream, this);
objectStream.Seek(, SeekOrigin.Begin);
return formatter.Deserialize(objectStream) as Employee;
}
} /// <summary>
/// 浅拷贝 (这个很容易理解)
/// </summary>
/// <returns></returns>
public Employee ShallowClone()
{
return Clone() as Employee;
}
} [Serializable]
class Department
{
public string Name{get;set;}
public override string ToString()
{
return this.Name;
} public Department(string name)
{
Name = name;
}
}

MemberwiseClone :创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,

则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象。因此,原始对象及其本副本引用的是同一对象。

为了实现深度复制,我们就必须便利有相互引用的对象构成的图,并需要处理其中的循环引用结构。这无疑是复杂的。幸好借助.Net的序列化

和反序列化机制,可以十分简单的深度clone一个对象。

反序列化深拷贝原理:

首先将对象序列化到内存流中,此时对象和对象引用的所有对象的状态都被保存到内存中。.Net的序列化机制会自动处理循环引用的情况。

然后将内存流中的状态信息反序列化到一个新的对象中。这样一个对象的深度复制就完成了。

注:类前面,以及类中引用的类,一定要加上 Serializable

参考:https://www.cnblogs.com/zhili/p/DeepCopy.html

      https://blog.csdn.net/gooodiuck/article/details/52299229

9.Dynamic

  始终使用dynamic来简化反射实现。(了解一下)

题外话:这本书真的真的很不错啊,很适合进阶,总结。虽然里面一些东西自己都是大概知道,但是它刚刚好起了,深入重点,总结的作用。真的很好的一本书,但是淘宝销量好像很差......

C# 《编写高质量代码改善建议》整理&笔记 --(一)基本语言篇的更多相关文章

  1. C# 《编写高质量代码改善建议》整理&笔记 --(六)编码规范及习惯

    一.命名规范 1.考虑在命名空间中使用复数 System.AllCollections System.TheCollection 2.用名词和名词组给类型命名 ScoreManager UserCon ...

  2. C# 《编写高质量代码改善建议》整理&笔记 --(五)成员设计

    1.可以字段应该重构为属性 2.谨慎将数组或集合作为属性 数组和集合作为属性存在会引起这样的一个分歧:如果属性是只读的,我们通常会认为他是不可改变的.但是如果将只读属性应用于数组和集合,而元素的内容和 ...

  3. C# 《编写高质量代码改善建议》整理&笔记 --(三)泛型&委托&事件

    1.泛型 基于泛型,我们可以将类型参数化,以便更大范围地进行代码复用.同时,它减少了泛型类及泛型方法中的转型, 确保类型安全. 1)总是优先考虑泛型 优点:可重用性,类型安全,高效率. 2)避免在泛型 ...

  4. C# 《编写高质量代码改善建议》整理&笔记 --(五)类型设计

    1.区分接口和抽象类的应用场合 区别: ①接口支持多继承,抽象类则不能. ②接口可以包含方法,属性,索引器,事件的签名,但不能有实现,抽象类则可以. ③接口在增加新方法后,所有的继承者都必须重构,否则 ...

  5. C# 《编写高质量代码改善建议》整理&笔记 --(四)资源管理&序列化

    1.显示释放资源需继承接口IDisposable 什么是资源:C#中每一个类型都代表一种资源,而资源又分为以下两类. 托管资源:由CLR管理分配和释放的资源,即从CLR里new出来的对象. 非托管资源 ...

  6. 编写高质量代码改善C#程序的157个建议——建议149:使用表驱动法避免过长的if和switch分支

    建议149:使用表驱动法避免过长的if和switch分支 随着代码变得复杂,我们很容易被过长的if和switch分支困扰. 一个类枚举类型Week如下: enum Week { Monday, Tue ...

  7. 编写高质量代码改善C#程序的157个建议——建议1:正确操作字符串

    最近拜读了陆敏技老师的<编写高质量代码改善C#程序的157个建议>,感觉不错,决定把笔记整理一遍. 建议1: 正确操作字符串 字符串应该是所有编程语言中使用最频繁的一种基础数据类型.如果使 ...

  8. 博友的 编写高质量代码 改善java程序的151个建议

    编写高质量代码 改善java程序的151个建议 http://www.cnblogs.com/selene/category/876189.html

  9. 编写高质量代码--改善python程序的建议(六)

    原文发表在我的博客主页,转载请注明出处! 建议二十八:区别对待可变对象和不可变对象 python中一切皆对象,每一个对象都有一个唯一的标识符(id()).类型(type())以及值,对象根据其值能否修 ...

随机推荐

  1. 2018-4-5-cadence skill

    skill 是 Cadence 提供的二次开发语言,可以做很多有用的二次开发. 开发参考手册:<algroskill><sklangref><sklanguser> ...

  2. 平时作业七 Java

    以下是几本计算机书籍的基本信息编号 书名 价格 出版社1 JAVA基础 32 清华大学出版社2 JAVA WEB开发 40 电子工业出版社3 面向对象程序设计 28 清华大学出版社4 Struts开发 ...

  3. kaggle之泰坦尼克号乘客死亡预测

    目录 前言 相关性分析 数据 数据特点 相关性分析 数据预处理 预测模型 Logistic回归训练模型 模型优化 前言 一般接触kaggle的入门题,已知部分乘客的年龄性别船舱等信息,预测其存活情况, ...

  4. php |= 什么意思

  5. 完整版本的推箱子小游戏,最简单的纯C语言打造

    /* 推箱子小游戏 1.定义绘制样式 用二维数组的方式 2.绘制图像 3.找出当前位置 4.逻辑判断,制造动作 根据数学xy轴的规律,这里使用ij 上移,行轴上升,行数减少 下移,行数下降,函数增加 ...

  6. iOS 获取app进程被杀死事件

    程序被用户双击上滑杀死后,就对app做一些特殊的处理 下面的方法可以获取到用户双击上滑杀死的事件 - (void)applicationDidEnterBackground:(UIApplicatio ...

  7. jquery固定表头和列头

    1.对网上的开源方法稍作了些修改 <script type="text/javascript">// <![CDATA[ function FixTable(Ta ...

  8. git逻辑和基本命令

    提交和推送的区别 提交(commit):把您做的修改,保存到本地仓库中 推送(push):把您本地仓库的代码推送至服务器(一般是远程服务器及gitlab或github) 拉取和获取的区别 git  p ...

  9. 按月分表(create table)

    PHP 按月分表控制台命令(yii2版) <?php /** * @Purpose: 按月分表脚本 * @User: Chrdai * @Date: 2019/3/19 * @Time: 15: ...

  10. java DTO对象与PO对象的相互转换

    2018-09-27 10:27:50 前言: 在实际开发中往往需要DTO对象与PO对象的相互转换: 先说说什么是DTO对象吧,个人觉得DTO就是PO的扩展而已,PO专门指向数据库,DTO作扩展(字段 ...