zoukankan      html  css  js  c++  java
  • C# 《编写高质量代码改善建议》整理&笔记 --(一)基本语言篇

     题记:这是自己的观后感,工作两年了,本来打算好好学习设计模式,或者作为客户端深入了解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 = "111" + s1 + "222";
                //创建了3个字符串对象,(开辟三个内存空间),并调用一次string.Contract
            }
            
            private static void Method2()
            {
                string s1 = 9 + "222";
                //发生一次装箱操作,调用一次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(2, 10));
                salarys.Add(new Salary(21, 1210));
                salarys.Add(new Salary(22, 10));
                salarys.Add(new Salary(233, 120));
                salarys.Sort();
                for (int i = 0; i < salarys.Count; i++)
                {
                    Console.WriteLine(salarys[i].BaseSalary);
                }
                salarys.Sort(new SortSalaryBonus());
                for (int i = 0; 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("111");
       Person p2 = new Person("111");
     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(0, 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来简化反射实现。(了解一下)

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

  • 相关阅读:
    CSS知识点:清除浮动
    CSS3的应用,你学会了吗?
    必须掌握的JavaScript基本知识
    一个搜索框的背后
    mybatis 中 foreach collection的三种用法
    基于Pipe的PureMVC FLEX框架的多核共享消息技术
    Flex与Java通信之HttpService
    python + eclipse + django + postgresql 开发网站(二)
    python + eclipse + django + postgresql 开发网站(一)
    [转]Python集合(set)类型的操作
  • 原文地址:https://www.cnblogs.com/u3ddjw/p/9039549.html
Copyright © 2011-2022 走看看