C#规范整理·语言要素

若有不理解,请留言,开始!

1. 正确操做字符串

  • 拼接字符串必定要考虑使用 StringBuilder ,默认长度为16,实际看状况设置。
  • StringBuilder本质: 是以非托管方式分配内存。
  • 同时StringFormat方法 内部也是使用StringBuilder进行字符串格式化。

2. 使用默认转型方法

  1. 类型的转换运算符 :每一个类型内部都有一个方法(运算符),分为隐式转换和显示转换。
    本身实现隐式转换:
puclic static implicit operator Ip(string ip) 
 {
      Ip iptemp=new Ip(ip);
      return iptemp;
 }
  1. 使用类型内置的Parse、TryParse、 ToString、ToDouble、 ToDateTime算法

  2. 使用帮助类提供的方法: System.Convert类、 System.BitConverter类来进行类型的转换。
  3. 使用CLR支持的类型:父类和子类之间的转换。数据库

3. 区别对待强制转型与as和is

为了编译更强壮的代码,建议更常使用as和is安全

何时使用aside

  • 若是类型之间都上溯到了某个共同的基类,那么根据此基类进行的转型(即基类转型为子类自己)应该使用as。子类与子类之间的转型,则应该提供转换操做符,以便进行强制转型。
    as操做符永远不会抛出异常,若是类型不匹配(被转换对象的运行时类型既不是所转换的目标类型,也不是其派生类型),或者转型的源对象为null,那么转型以后的值也为null。

何时使用is函数

  • as操做符有一个问题,即它不能操做基元类型。若是涉及基元类型的算法,就须要经过is转型前的类型来进行判断,以免转型失败。

4.TryParse比Parse好

这个确定好,不说了。安全ui

5.使用int?来确保值类型也能够为null

基元类型为何须要为null?考虑两个场景:this

  1. 数据库支持整数可为空
  2. 数据在传输过程当中存在丢失问题,致使传过来的值为null

写法: int?i=null;code

语法T?是Nullable<T>的简写,二者能够相互转换。能够为null的类型表示其基础值类型正常范围内的值再加上一个null值。例如,Nullable<Int32>,其值的范围为-2 147 483 648~2 147 483 647,再加上一个null值。orm

?常常和??配合使用,好比:对象

int?i=123;
 int j=i??0;

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

使用const的理由只有一个,那就是效率。之因此说const变量的效率高,是由于通过编译器编译后,咱们在代码中引用const变量的地方会用const变量所对应的实际值来代替。好比: const=100, const和100被使用的时候是等价,const自带static光圈。
const和readonly的本质区别以下:

  1. const是编译期常量,readonly是运行期常量
  2. const只能修饰基元类型、枚举类型或字符串类型,readonly没有限制。
  • 注意:在构造方法内,能够屡次对readonly赋值。即在初始化的时候。

7.将0值做为枚举的默认值

容许使用的枚举类型有byte、sbyte、short、ushort、int、uint、long和ulong。应该始终将0值做为枚举类型的默认值。不过,这样作不是由于容许使用的枚举类型在声明时的默认值是0值,而是有工程上的意义。
既然枚举类型从0开始,这样能够避免一个星期多出来一个0值。

8.避免给枚举类型的元素提供显式的值

不要给枚举设定值。有时候有某些增长的须要,会为枚举添加元素,在这个时候,就像咱们为枚举增长元素ValueTemp同样,极有可能会一不当心增长一个无效值。

9.习惯重载运算符

好比:Salary familyIncome=mikeIncome+roseIncome; 阅读一目了然。经过使用opera-tor关键字定义静态成员函数来重载运算符,让开发人员能够像使用内置基元类型同样使用该类型。

10.建立对象时须要考虑是否实现比较器

有特殊须要比较的时候就考虑。集合排序比较经过linq 也能够解决。

11.区别对待==和Equals

不管是操做符“==”仍是方法“Equals”,都倾向于表达这样一个原则:

  1. 对于值类型,若是类型的值相等,就应该返回True。
  2. 对于引用类型,若是类型指向同一个对象,则返回True。

    注意 

  • 因为操做符“==”和“Equals”方法从语法实现上来讲,均可以被重载为表示“值相等性”和“引用相等性”。因此,为了明确有一种方法确定比较的是“引用相等性”,FCL中提供了Object.ReferenceEquals方法。该方法比较的是:两个示例是不是同一个示例。
  • 对于string这样一个特殊的引用类型,微软以为它的现实意义更接近于值类型,因此,在FCL中,string的比较被重载为针对“类型的值”的比较,而不是针对“引用自己”的比较。

12.重写Equals时也要重写GetHashCode

  • 除非考虑到自定义类型会被用做基于散列的集合的键值;不然,不建议重写Equals方法,由于这会带来一系列的问题。
    集合找到值的时候本质上是先去 查找HashCode,而后才查找该对象来比较Equals

注意
重写Equals方法的同时,也应该实现一个类型安全的接口IEquatable<T>,好比 :class Person:IEquatable

13.为类型输出格式化字符串

有两种方法能够为类型提供格式化的字符串输出。

  1. 一种是意识到类型会产生格式化字符串输出,因而让类型继承接口IFormattable。这对类型来讲,是一种主动实现的方式,要求开发者能够预见类型在格式化方面的要求。
  2. 更多的时候,类型的使用者需为类型自定义格式化器,这就是第二种方法,也是最灵活多变的方法,能够根据需求的变化为类型提供多个格式化器。

一个典型的格式化器应该继承接口IFormatProvider和ICustomFomatter

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

浅拷贝 

将对象中的全部字段复制到新的对象(副本)中。其中,值类型字段的值被复制到副本中后,在副本中的修改不会影响到源对象对应的值。而引用类型的字段被复制到副本中的是引用类型的引用,而不是引用的对象,在副本中对引用类型的字段值作修改会影响到源对象自己。

深拷贝 

一样,将对象中的全部字段复制到新的对象中。不过,不管是对象的值类型字段,仍是引用类型字段,都会被从新建立并赋值,对于副本的修改,不会影响到源对象自己。

不管是浅拷贝仍是深拷贝,微软都建议用类型继承IClone-able接口的方式明确告诉调用者:该类型能够被拷贝。固然,ICloneable接口只提供了一个声明为Clone的方法,咱们能够根据需求在Clone方法内实现浅拷贝或深拷贝。

  • 一个简单的浅拷贝的实现代码以下所示:
class Employee:ICloneable
 {    
       public string IDCode {get;set;}   
       public int Age {get;set;  }
       public Department Department{get;set;}    

    #region ICloneable成员 
    public object Clone() 
   {       
    return this.MemberwiseClone(); 
    } 

   #endregion
 
}

class Department
{    
    public string Name {get;set;}   
    public override string ToString() 
    {      
     return this.Name;  
    }
}

注意到Employee的IDCode属性是string类型。理论上string类型是引用类型,可是因为该引用类型的特殊性(不管是实现仍是语义),Object.MemberwiseClone方法仍旧为其建立了副本。也就是说,在浅拷贝过程,咱们应该将字符串当作是值类型。

  • 一个简单的深拷贝实现样例以下(建议使用序列化的形式来进行深拷贝)
class Employee:ICloneable
{    
 public string IDCode{get;set;}   
 public int Age{get;set;}    
 public Department Department{get;set;}   

#region ICloneable成员    
public object Clone()    
{        
 using(Stream objectStream=new MemoryStream())        
{            
 IFormatter formatter=new BinaryFormatter();            
 formatter.Serialize(objectStream,this);            
 objectStream.Seek(0,SeekOrigin.Begin);            
 return formatter.Deserialize(objectStream)as Employee;        
}   
}   
 #endregion}

同时实现深拷贝和浅拷贝

因为接口ICloneable只有一个模棱两可的Clone方法,因此,若是要在一个类中同时实现深拷贝和浅拷贝,只能由咱们本身实现两个额外的方法,声明为DeepClone和Shallow。Em-ployee的最终版本看起来应该像以下的形式:

[Serializable]
class Employee:ICloneable
{   
 public string IDCode{get;set;}    
 public int Age{get;set;}    
 public Department Department{get;set;}   
 #region ICloneable成员    
 public object Clone()   
 {       
  return this.MemberwiseClone();   
  }    

#endregion   
  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;       
    }   
 }  

  public Employee ShallowClone()   
  {       
     return Clone()as Employee;    
   }}

14.利用dynamic来简化反射实现

dynamic是Framework 4.0的新特性。dynamic的出现让C#具备了弱语言类型的特性。编译器在编译的时候再也不对类型进行检查,编译器默认dynamic对象支持开发者想要的任何特性。
好比,即便你对GetDynamicObject方法返回的对象一无所知,也能够像以下这样进行代码的调用,编译器不会报错:

dynamic dynamicObject=GetDynamicObject();
Console.WriteLine(dynamicObject.Name);
Console.WriteLine(dynamicObject.SampleMethod());

固然,若是运行时dynamicObject不包含指定的这些特性(如上文中带返回值的方法SampleMethod),运行时程序会抛出一个RuntimeBinderException异常:“System.Dynamic.ExpandoObject”未包含“Sam-pleMethod”的定义。

var与dynamic有巨大的区别

  • var是编译器的语法糖
  • dynamic是运行时解析,在编译期时,编译器不对其作任何检查。

反射使用

  • 不使用dynamic方式
DynamicSample  dynamicSample=new  DynamicSample();
var addMethod=typeof(DynamicSample).GetMethod("Add");
int re=(int)addMethod.Invoke(dynamicSample,new object[] {1,2});
  • 使用dynamic方式
dynamic dynamicSample2=new DynamicSample();
int re2=dynamicSample2.Add(1,2);

//在使用dynamic后,代码看上去更简洁了,而且在可控的范围内减小了一次拆箱的机会。经验证,频繁使用的时候,消耗时间更少

建议:始终使用dynamic来简化反射实现。

总结

在大部分应用状况下,“效率”并无那么高的地位,灵活性更重要。在部分状况下,“灵活性”并无那么高的地位,效率最重要。

相关文章
相关标签/搜索