C# 程序员最常犯的 10 个谬误http://www.oschina.net/translate/top-10-mistakes-that-c-sharp-programmers-make

运用伸张方法确实有优势,但也会让那多少个对它不精晓照旧认识不正确的开发者头痛,浪费时间。更加是在看在线示例代码,或者别的已经写好的代码的时候。当那几个代码发生编译错误(因为它调用了那多少个分明没在被调用类型中定义的方式),一般的倾向是考虑代码是还是不是采用于所引述类库的任何版本,甚至是不一致的类库。很多年华会被花在找新本子,或者被认为“丢失”的类库上。

Array,ArrayList,BitArray,BitVector32,Dictionary<K,V>,HashTable,HybridDictionary,List<T>,NameValueCollection,OrderedDictionary,Queue, Queue<T>,SortedList,Stack, Stack<T>,StringCollection,StringDictionary.

有关本文

一个宽广的例证是,你改改了您的代码,并移除了对少数变量的使用,可是,你忘了移除该变量的扬言。程序可以很好的运作,不过编译器会提示有未接纳的变量。程序能够很好的周转使得部分程序员不去修补警告。更有甚者,有些程序员很好的施用了Visual
Studio中“错误列表”窗口的隐藏警告的意义,很简单的就把警告过滤了,以便专注于错误。不用多久,就会累积一堆警告,那一个警告都被“惬意”的疏忽了(更糟的是,隐藏掉了)。

decimal total=(from accout in myaccouts
    where accout.status=='active'
    select accout.Balance).sum();

上边的例子演示了别的一组广泛的法门,一种会抛出尤其,而另一种则不会:

除此以外,即便你驾驭要对方法2的重返值进行判定,假使您发觉值为空,接下去你会如何是好?在这一个艺术中报告错误合适吧?假诺类型转换败北了你还有其余的主意去品尝吧?即使没有的话,那么抛出这一个分外是唯一正确的选料,并且丰裕的抛出点离其暴发点越近越好。


当C#编译器碰着一个目的的实例调用了一个艺术,并且它在这么些目的的类中找不到相当方式,它就会尝试在功能域中享有的增添方法里找一个匹配所需求的类和办法签名的。要是找到了,它就把实例的引用当做第三个参数传给这些增加方法,然后一旦有任何参数的话,再把它们依次传入增加方法。(如若C#编译器没有在功用域中找到呼应的恢弘方法,它会抛措。)

即使数据库的查询操作是LINQ的一个不行出众的应用,然而它一律可以利用于各样可枚举的聚众对象。(如:任何达成了IEnumerable接口的对象)。举例来说,若是你有一个Account类型的数组,不要写成上面那样:

就像先前波及的,LINQ状态信赖于IEnumerable接口的兑现目的,比如,上边的简短函数会协商帐户集合中的帐户余额:

率先个Equals方法(没有comparisonType那参数)和利用==操作符的结果是平等的,但便宜是,它显式的指明了相比较类型。它会按顺序逐字节的去比较字符串。在很多动静下,那正是你所期待的相比较类型,尤其是当相比一些因而编程设置的字符串,像文件名,环境变量,属性等。在那么些景况下,只要按梯次逐字节的可比就足以了。使用不带comparisonType参数的Equals方法举行相比的唯一不佳的地点在于那多少个读你程序代码的人或者不掌握你的相比类型是何许。

对C#编译器来说,增加方法是个“语法糖”,使咱们能把代码写得更显然,更便于维护(多数状态下)。分明,前提是您领悟它的用法,否则,它会相比便于令人迷惑,尤其是一早先。

一种艺术是去看一下对象是怎么着定义的(在Visual
Studio中,你可以把光标放在对象的名字上,并按下F12键)

在伸张方法的名字和类中定义的方法的名字一样,只是在章程签名上有微小区其他时候,甚至那一个熟稔扩张方法的开发者也偶尔犯上面的谬误。很多时刻会被花在搜寻“不存在”的拼写错误上。

(此外必要提出的是,定义扩张方法的IEnumerable接口和Enumerable类的名字间的相似性没什么奇怪的。那种相似性只是随意的作风选用。)

  • 当相比较用户输入的字符串或者将字符串相比结实突显给用户时,使用本地化的可比(CurrentCulture
    或者CurrentCultureIgnoreCase)。
  • 当用于程序设计的相比字符串时,使用原来的可比(Ordinal 或者
    OrdinalIgnoreCase)
  • InvariantCulture和InvariantCultureIgnoreCase一般并不选择,除非在受限的地步之下,因为本来的相比普通功能更高。假若与本地文化有关的可比是必需的,它应有被实践成基于近来的学识或者另一种独特文化的可比。

大面积错误#8:遗漏资源自由

答案是,当在LINQ语句中的基础对象都引用到SQL表中的数据(如与在这些例子中,在实业框架为DbSet的目的的事态下),该语句被转换成一个T-SQL语句。然后根据的T-SQL的规则,而不是C#的平整,所以在上述情况下的相比较停止是不区分轻重缓急写的。

  decimal total = (from account in myAccounts
               where account.Status == "active"
               select account.Balance).Sum();

本来,你也得以写你协调的恢宏方法。可是必须意识到尽管扩张方法看起来和其余实例方法同样被调用,但那实则只是幻觉。事实上,增加方法不可以访问所扩充类的个人和护卫成员,所以它不可能被看成传统延续的替代品。

Console.WriteLine(point1.IsEmpty);        // True

最安全的实践是接连为Equals方法提供一个comparisonType的参数。

C++
和其余过多语言的程序员,习惯了给变量赋值的时候,要么赋单纯的值,要么是并存对象的引用。但是,在C#
中,是值如故援引,是由写那个目的的程序员决定的,而不是实例化对象并赋值的程序员决定的。那往往会坑到
C# 的新手程序员。

一般景况下,固然LINQ是一个有益的和均等的主意来查询对象的聚集,在实际中您还索要领会您的话语是或不是会被翻译成什么,来确保您的代码的行事将如预期运行。

别的,对Equals
方法来说,字符串也不足为奇提供了Compare方法,可以提供字符串的相对顺序音讯而不光测试是不是等于。这么些法子可以很好适用于<,
<=, >和>= 运算符,对上述议论同样适用。

  class Program {
      static Point point1;      static Pen pen1;      static void Main(string[] args) {
          Console.WriteLine(pen1 == null);      // True
          Console.WriteLine(point1 == null);    // False (huh?)
      }
  }


  using (FileStream myFile = File.OpenRead("foo.txt")) {
    myFile.Read(buffer, 0, 100);
  }
  int.Parse();     // 如果参数无法解析会抛出异常
  int.TryParse();  // 返回bool值表示解析是否成功

  IEnumerable.First();           // 如果序列为空,则抛出异常
  IEnumerable.FirstOrDefault();  // 如果序列为空则返回 null 或默认值

如您所见,即使Point和Pen对象的创造情势相同,可是当一个新的X的坐标值被分配到point2时,
point1的值保持不变
。而当一个新的color值被分配到pen2,pen1也随着变动。因而,我们得以推论point1和point2每个都包罗自己的Point对象的副本,而pen1和pen2引用了同一个Pen对象
。若是没有这么些测试,我们怎么可以明白这么些原理?

警戒的面世是有缘由的。所有C#的编译错误都标明你的代码有通病,同样,一些警示也是那般。那两者之间的界别在于,对于警告的话,编译器能够依据你代码的提醒工作,不过,编译器发现你的代码有一点小标题,很有可能会使您的代码不可能按照你的料想运行。



纵然那是一个很简短的例子,在多少情形下,一个十足的LINQ语句可以任意地更迭掉你代码中一个迭代轮回(或嵌套循环)里的几十条语句。更少的代码常常意味着暴发Bug的机遇也会更少地被引入。不过,记住,在质量方面可能要权衡一下。在性质很关键的情景,尤其是您的迭代代码可以对您的汇集进行借使时,LINQ做不到,所以自然要在那二种格局之间相比一下质量。

在地点示例中运用using语句,你就可以规定myFile.Dispose()方法会在文书使用完之后被当下调用,不管Read()方法有没有抛非凡。

在C#中,值得类型无法为空。按照定义,值的连串值,甚至早先化变量的值类型必须有一个值。这就是所谓的该品种的默认值。那寻常会招致以下,意料之外的结果时,检查一个变量是不是未伊始化:

关于C#

可是在有点景况下,有太多的接纳和没有丰富的抉择相同不好,集合类型也是那般。数量很多的取舍余地肯定可以保险是您的办事健康运转。可是你最好或者花一些岁月提前查找并问询一下汇集类型,以便采纳一个最符合你必要的聚合类型。那最后会使您的次第品质更好,减少失误的可能。

为了利用好C#中的类型安全,你最好选取使用一个泛型接口,而不是选拔非泛型的假说。泛型接口中的元素类型是您在在评释对象时指定的门类,而非泛型中的元素是object类型。当使用一个非泛型的接口时,C#的编译器不能对你的代码举行项目检查。同样,当您在操作原生类型的集纳时,使用非泛型的接口会导致C#对那些系列举行多次的装箱(boxing)和拆箱(unboxing)操作。和行使指定了卓越类型的泛型集合比较,那会牵动很肯定的属性影响。

而不是这么:

 public interface IEnumerable<out T> : IEnumerable 
 {
      IEnumerator<T> GetEnumerator();
 }
  public decimal SumAccounts(IEnumerable<Account> myAccounts) 
  {
      return Enumerable.Sum(myAccounts, a => a.Balance);
  }

一旦有一个成团指定的因素类型(如string或bit)和你正在操作的均等,你最好优先选项选择它。当指定相应的因素类型时,那种集合的作用更高。

答案是Sum并不在IEnumerable接口内定义,而是一个

对此拍卖抽象操纵集合义务,LINQ无疑是高大的。无论他们是在内存的靶子,数据库表,或者XML文档。在这样一个健全球中间,你不需要精通底层对象。可是倘使我们生活在一个完美世界中间可能会带来错误。事实上,当在准确的一律数量上推行时,假使该多少碰巧在一个两样的格式之中相同的,LINQ语句能回到不一致的结果

如上所示,在C#中,struct关键字是用来定义一个值类型,而class关键字是用来定义引用类型的。
对于那么些有C++编程背景人来说,若是被C++和C#里面某些类似的严重性字搞混,可能会对以上那种表现感到很受惊。

当然了,任何规则都有两样。所以,有些时候,即便您的代码在编译器看来是有点难题的,但是那正是你想要的。在那种很少见的景况下,你最好应用
#pragma warning disable [warning id]
把吸引警告的代码包裹起来,而且只包裹警告ID对应的代码。这会且只会幸免对应的告诫,所以当有新的警示爆发的时候,你仍然会知道的。.
总结


譬如,下边演示了C#中开展浮现类型转换的三种分裂的办法:

  if (int.TryParse(myString, out myInt)) 
  {
      // use myInt
  }
  else 
  {    
      // use default value
  }

就算有成百上千程序员使用==操作符来比较字符串,不过那种方法其实是最不推荐应用的。首要缘由是出于那种办法没有在代码中显式地指定使用哪一类档次去相比较字符串。
相反,在C#中判断字符串是或不是等于最好使用Equals方法:

即使你想要依赖的行事会因值类型和引用类型而异,举例来说,倘若您想把一个对象作为参数传给一个艺术,并在那一个情势中修改那些目的的景况。你势须要保管您在拍卖正确的系列对象。

  try 
  {
    myInt = int.Parse(myString);    // use myInt
  }
  catch (FormatException) 
    // use default value
  }

一些C#的程序员甚至都不了然LINQ的留存,好在不领会的人正在逐年裁减。然而还有些人误以为LINQ只用在数据库查询中,因为LINQ的严重性字和SQL语句实在是太像了。

好啊,如若myaccout是Dbset的目的。(默许设置了分化界别轻重缓急写的安顿),where表明式仍会协作该因素。可是,倘若myaccout是在内存阵列之中,那么它将不包容,由此将生出不一致的总的结果。

  class Account 
  {

      int myId;      
      int Id;   // 编译器已经警告过了,但是你不听

      // Constructor
      {          
         this.myId = Id;     // OOPS!
      }

  }

在C#
3.0中,LINQ的引入改变了大家过去对聚集对象的询问和改动操作。从那以后,你应有用LINQ去操作集合,而不是由此迭代的主意。

另一个常见的圈套是自己去完成一个集结类型。那并不是说永远不要那样做,你能够由此选取或伸张.NET提供的片段被广泛使用的见面类型来节省大批量的日子,而不是去重新造轮子。
更加是,C#的C5 Generic Collection Library
和CLI提供了无数极度的聚众类型,像持久化树形数据结构,基于堆的预先级队列,哈希索引的数组列表,链表等以及越来越多。

唯独Sum方法应该定义到何处?C#是强类型的言语,因而若是Sum方法的引用是船到江心补漏迟的,C#编译器会对其报错。大家通晓它必须存在,不过相应在何地吗?别的,LINQ提供的供查询和聚集结果有所办法在什么地方定义呢?

设想一下,该目的之一的账号会爆发哪些。状态相当“Active”(注意大写A)?

方今,你的代码中有了一个严重的谬误(但是编译器只是出口了一个警戒,其缘由现已表达过),那会浪费你大批量的时光去找寻那错误,具体景况由你的次第复杂程度决定。如若您一伊始就专注到了这些警示,你只须要5分钟就足以修改掉,从而幸免那么些标题。

然则,那并不表达 TryParse
方法更好。某些情状下适合,某些意况下则不相符。那就是干吗有三种方法供大家挑选了。按照你的具体情状选拔恰当的措施,并切记,作为一个开发者,十分是完全能够改为您的情侣的。

广泛的荒谬#2:误会未开首化变量的默认值


普遍错误 #9: 回避很是

来源:http://www.oschina.net/translate/top-10-mistakes-that-c-sharp-programmers-make

科普错误 #6:对扩充方法感到思疑或者被它的款式诈骗

明亮了那一点,大家可以看来地点介绍的sumAccounts方法能以下边的主意完毕:

增加方法通过在文件开头添加using
[namespace];引入到功用域。你必要了然你要找的恢弘方法所在的名字空间。如果您驾驭你要找的是何许,那一点很不难。

大规模错误 #7: 对手头上的任务拔取不当的联谊类型

举个有血有肉的例证,某些意况下当极度暴发时,你有另一个可选的办法(如,默许值),那么,选择不抛出很是的点子是一个相比好的挑选。在这种景观下,你最接近上边那样写:

缘何不是【point
1】空?答案是,点是一个值类型,和默许值点(0,0)一样,没有空值。
那是一个非凡简单和广泛的不当。在C#中诸多(可是否成套)值类型有一个【IsEmpty】属性,你可以看看它是不是等于默许值:


熟悉C#的有些最主要的细小之处,像本文中所提到的这几个(但不限于那个),可以帮助大家更好的去采纳语言,从而幸免有些广大的牢笼。

定义在System.Linq.Enumerable类中的static方法(叫做“extension method”)

  Point point1 = new Point(20, 30);
  Point point2 = point1;
  point2.X = 50;
  Console.WriteLine(point1.X);       // 20 (does this surprise you?)
  Console.WriteLine(point2.X);       // 50

  Pen pen1 = new Pen(Color.Black);
  Pen pen2 = pen1;
  pen2.Color = Color.Blue;
  Console.WriteLine(pen1.Color);     // Blue (or does this surprise you?)
  Console.WriteLine(pen2.Color);     // Blue

C#是达标微软国有语言运行库(CLR)的少数言语中的一种。完成CLR的言语可以受益于其带来的特点,如跨语言集成、十分处理、安全性增强、部件组合的简短模型以及调节和剖析服务。作为现代的CLR语言,C#是利用最为常见的,其利用场景针对Windows桌面、移入手机以及服务器环境等复杂、专业的成本项目。

就算本文商讨的绝半数以上不当是针对性 C# 的,有些错误与其余以 CLR
为目的的言语,或者选取了 Framework Class Library (FCL) 的语言也有关。

  public struct Point { … }     // defines a “value” type
  public class Pen { … }        // defines a “reference” type

即便所有C#的类中都定义了析构方法,可是销毁对象(C#中也叫做终结器)可能存在的难点是您不确定它们在时候被调用。他们在将来一个不确定的岁月被垃圾回收器调用(一个异步的线程,此举可能引发额外的面世)。试图防止那种由垃圾回收器中GC.Collect()方法所施加的强制限制并非一种好的编程实践,因为可能在垃圾堆回收线程试图回收适宜回收的对象时,在不可预见的时光内造成线程阻塞。

C#在运作时也会强制进行项目检查。相对于像C++那样会给错误的类型转换赋一个随机值的言语来说,C#那能够使你更快的找到出错的义务。然则,程序员再三遍无视了C#的这一特性。由于C#提供了二种档次检查的法子,一种会抛出格外,而另一种则不会,那很可能会使她们掉进那几个“坑”里。有些程序员倾向于回避至极,并且认为不写
try/catch 语句可以省去一些代码。

  string s = "strasse";

  // outputs False:
  Console.WriteLine(s == "stra&szlig;e");
  Console.WriteLine(s.Equals("stra&szlig;e"));
  Console.WriteLine(s.Equals("stra&szlig;e", StringComparison.Ordinal));
  Console.WriteLine(s.Equals("Stra&szlig;e", StringComparison.CurrentCulture));        
  Console.WriteLine(s.Equals("stra&szlig;e", StringComparison.OrdinalIgnoreCase));

  // outputs True:
  Console.WriteLine(s.Equals("stra&szlig;e", StringComparison.CurrentCulture));
  Console.WriteLine(s.Equals("Stra&szlig;e", StringComparison.CurrentCultureIgnoreCase));

C#提供了大批量的成团类型的对象,上边只列出了其中的一部分:

public decimal SumAccounts(IEnumerable<Account> myAccounts) 
{      
   return myAccounts.Sum(a => a.Balance);
}

以此荒唐并不是C#所特有的,可是在C#中那种状态却相比较多,尤其是从C#编译器弃用了严俊的门类检查之后。

您只要这么写:

C#是一门强大的还要很灵敏的语言,它有众多机制和言语专业来家喻户晓的狠抓你的生产力。和任何语言一样,要是对它能力的摸底有限,那很可能会给您带来阻碍,而不是补益。正如一句谚语所说的那样“knowing
enough to be
dangerous”(译者注:意思是自以为已经精通丰盛了,可以做某事了,但骨子里不是)。

科普错误 #3: 使用不适合或未指定的艺术比较字符串

事实上大家兴许早就这么已毕了那几个方法,而不是问什么要有恢宏方法。伸张方法本身只是C#的一个利于你无需延续、重新编译或者涂改原始代码就足以给已存的在档次“添加”方法的格局。

C#是种面向对象的强类型语言。C#在编译和运转时都有些强类型检查,使在半数以上一流的编程错误可以被尽早地窥见,而且地点一定一定精准。比较于那一个不拘泥类型,在违法操作很久后才报出可追踪到莫明其妙错误的语言,那足以为程序员节省不可胜言岁月。可是,许多程序员有意或下意识地摒弃了这些检测的多少,那导致本文中钻探的部分标题。

再加上选择了编辑器的智能感知的效益,那种颠倒是非就很有可能暴发。

大规模错误 #10: 累积编译器警告而不处理

若是您不了然您正在利用的目标是或不是是值类型或引用类型,你也许会遇见一些惊喜。例如:

只是,假使你不经意掉这一类的警戒,类似于上边那一个事例迟早会并发在你的代码中。

CLR
托管环境扮演了排泄物回收器的角色,所以你不须求显式释放已成立对象所占有的内存。事实上,你也不可能显式释放。C#中尚无与C++
delete对应的运算符或者与C语言中free()函数对应的办法。但那并不意味你能够忽略所有的应用过的目标。许多目标类型封装了广大别样门类的系统资源(例如,磁盘文件,数据连接,互连网端口等等)。保持那一个资源利用情形会猛烈耗尽系统的资源,削弱质量并且最后致使程序出错。

上面是有的骨干的辅导标准:

动用带comparisonType的Equals方法去比较字符串,不仅会使你的代码更清楚,还会使您去考虑清楚要用哪个种类档次去比较字符串。那种格局非凡值得你去行使,因为即使在菲律宾语中,按梯次举行的相比较和按语言区域举行的可比之间并没有太多的差别,但是在其它的一对语种可能会有很大的不等。假使您不经意了那种可能,无疑是为你协调在未来的道路上挖了成百上千“坑”。举例来说:

多少程序员认为“非凡有害”,所以她们任其自然的以为不抛出分外的程序显得越发“高大上”。即使在一些意况下,那种意见是不利的,但是那种观点并不适用于所有的情况。

decimal total = 0;  
foreach (Account account in myAccounts) 
{    
     if (account.Status == "active") 
     {
        total += account.Balance;
     }
}

大规模误区 #4: 使用迭代式 (而不是申明式)的语句去操作集合

那并表示最好不用用终结器,显式释放资源并不会招致其中的别样一个结果。当你打开一个文书、互连网端口或者数额连接时,当您不再采用这么些资源时,你应该尽早的显式释放这个资源。

 namespace System.Linq 
 {
    public static class Enumerable 
    {      ...
      // the reference here to “this IEnumerable<TSource> source” is
      // the magic sauce that provides access to the extension method Sum
      public static decimal Sum<TSource>(this IEnumerable<TSource> source,Func<TSource, decimal> selector);      ...
    }
 }

在上边的代码中,myAccounts参数的门类被声称为IEnumerable<Account>,myAccounts引用了一个Sum
方法 (C# 使用类似的 “dot notation”
引用方法或者接口中的类),我们意在在IEnumerable接口中定义一个Sum()方法。但是,IEnumerable没有为Sum方法提供任何引用并且只有如下所示的简要定义:

在C#中有为数不少艺术来比较字符串。

壮大方法的鲜明特点是首先个形参前的this修饰符。这就是编译器知道它是一个伸张方法的“奥妙”。它所修饰的参数的项目(那些事例中的IEnumerable)表明这么些类如故接口将显得落成了那个法子。


  // 方法 1:
  // 如果 account 不能转换成 SavingAccount 会抛出异常
  SavingsAccount savingsAccount = (SavingsAccount)account;

  // 方法 2:
  // 如果不能转换,则不会抛出异常,相反,它会返回 null
  SavingsAccount savingsAccount = account as SavingsAccount;

很明显,固然不对方法2重临的结果开展判定的话,最后很可能会爆发一个
NullReferenceException
的百般,那可能会现身在稍晚些的时候,那使得难题更难追踪。相比的话,方法1会即刻抛出一个
InvalidCastExceptionmaking,那样,难点的发源就很醒目了。

在C#中,用扩张方法变得更其流行。除了LINQ,在其它多个来源微软明日被广大应用的类库Unity
Application Block和Web API
framework中,也应用了伸张方法,而且还有很多其余的。框架越新,用扩大方法的可能性越大。

资源走漏几乎在富有的环境中都会掀起关切。不过,C#提供了一种健康的机制使资源的使用变得简单。若是合理利用,可以大大裁减走漏出现的机率。NET
framework定义了一个IDisposable接口,仅由一个Dispose()构成。任何完成IDisposable的接口的目的都会在目标生命周期截止调用Dispose()方法。调用结果肯定而且决定性的释放占用的资源。

public bool Equals(string value);  



public bool Equals(string value, StringComparison comparisonType);

比如说,请考虑上边的讲话:

等一会,在我们从前研究过的字符串相比较中, 大家看见 ==
操作符扮演的角色就是概括的可比. 所以,为啥在这么些规则下, ==
表现出的是其余的一个款式呢 ?

大规模错误 #1: 把引用当做值来用,或者反过来

#5常见错误:在LINQ语句之中没有考虑底层对象

纪事,要是你仔细看的话,你会发现,C#编译器给了您多多关于你程序健壮性的灵光的音信。不要忽略警告。你只需花几分钟的光阴就可以修复它们,当现身的时候就去修补它,那可以为您节省千千万万时光。试着为友好创设一种“洁癖”,让Visual
Studio 的“错误窗口”一向显示“0破绽百出,
0警告”,一旦出现警示就感觉到不好受,然后随即把警告修复掉。

本文描述了10个 C# 程序员常犯的错误,或相应避免的骗局。

若果在一个代码段中开创并释放一个指标,却忘记调用Dispose()方法,那是不可原谅的,因而C#提供了using语句以保险无论代码以什么的措施退出,Dispose()方法都会被调用(不管是可怜,return语句,或者简单的代码段甘休)。这些using和事先提到的在文书起头用来引入名字空间的同一。它有别的一个居多C#开发者都未曾意识的,完全不相干的指标,也就是保险代码退出时,对象的Dispose()方法被调用: