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

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

关于C#

C#大凡上微软官语言运行库(CLR)的个别言语中的平栽。达成CLR的言语可以受益于那个带的性状,如跨语言集成、异常处理、安全性增强、部件组合的简练模型和调节和剖析服务。作为当代底CLR语言,C#举凡运最常见的,其应用场景针对Windows桌面、移动手机和服务器环境等繁杂、专业的付出项目。

C#凡是种面向对象的强类型语言。C#在编译和运行时犹有强类型检查,使在大部超人的编程错误能够给抢地发现,而且位置固定一定精准。相比于那些不拘泥类型,在违规操作很遥远后才报发出而追踪至莫名其妙错误的语言,这足以呢程序员节省成千上万时间。然而,许多程序员有意还是无意识地废弃了之检测的略微,这造成本文中讨论的组成部分问题。

至于本文

本文描述了10个 C# 程序员常犯的错,或该避免的牢笼。

尽管本文讨论的大部错误是针对 C# 的,有些错误和其它因 CLR
为对象的语言,或者以了 Framework Class Library (FCL) 的语言为系。


大错误 #1: 把援当做值来用,或者转

C++
和另外多言语的程序员,习惯了受变量赋值的下,要么赋单纯的价,要么是并存对象的援。然而,在C#
中,是价值还是引用,是由于写这目标的程序员决定的,而无是实例化对象并赋值的程序员决定的。这往往会坑至
C# 的初手程序员。

假如您免晓乃在使用的对象是否是值类型或引用类型,你或许会见遇见有的惊喜。例如:

  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

若你所表现,尽管Point和Pen对象的创办方式相同,但是当一个初的X的坐标值被分配至point2时时,
point1的值保持不转换
。而当一个初的color值被分配到pen2,pen1也随之更改。因此,我们得推断point1和point2每个都带有自己的Point对象的副本,而pen1和pen2引用了和一个Pen对象
。如果无此测试,我们怎么能知道这规律?

同等种艺术是错过押一下靶是怎样定义的(在Visual
Studio中,你可拿光标放在对象的名上,并依下F12键)

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

要达到所示,在C#面临,struct关键字是用来定义一个值类型,而class关键字是因此来定义引用类型的。
对于那些有C++编程背景人来说,如果为C++和C#中间某些类似之重要字来混,可能会见对上述这种作为感到老震惊。

假定您想如果依赖的所作所为会因值类型和援类型而异,举例来说,如果您想拿一个目标作为参数传被一个术,并于是方法吃修改者目标的状态。你一定要是力保您于处理是的色对象。


大规模的缪#2:误会未初始化变量的默认值

在C#吃,值得类型不克啊空。根据定义,值的品类值,甚至初始化变量的值类型必须出一个值。这就是所谓的该档的默认值。这便会招致以下,意想不到的结果时,检查一个变量是否不初始化:

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

缘何非是【point
1】空?答案是,点是一个值类型,和默认值点(0,0)一样,没有空值。
眼看是一个非常简单和宽广的错。在C#吃众多(但是非是全体)值类型有一个【IsEmpty】属性,你可以看看她是不是当默认值:

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

周边错误 #3: 使用无适用或未指定的法子比较字符串

在C#备受生出许多措施来比字符串。

虽然有众多程序员使用==操作符来比字符串,但是这种方式其实是无限不推荐使用的。主要因是出于这种措施没有当代码中显式地指定使用啊种类型去比字符串。
相反,在C#被判断字符串是否当最好以Equals方法:

public bool Equals(string value);  



public bool Equals(string value, StringComparison comparisonType);

率先只Equals方法(没有comparisonType这参数)和动==操作符的结果是一模一样的,但便宜是,它显式的指明了于类型。它见面照顺序逐字节的失比较字符串。在广大状下,这正是你所愿意的可比类型,尤其是当于有经过编程设置的字符串,像文件称,环境变量,属性等。在这些情况下,只要以顺序逐字节的比就足以了。使用不牵动comparisonType参数的Equals方法开展比较的唯一不好的地方在那些读你程序代码的口可能无懂得乃的较类型是什么。

使带来comparisonType的Equals方法去比较字符串,不仅会使你的代码更鲜明,还会见要你错过考虑清楚而为此啊种类型去比较字符串。这种办法十分值得你失去动,因为尽管当英语受到,按梯次进行的比和遵循语言区域开展的较中并不曾太多之分,但是以另外的局部语种可能会见起坏可怜之不同。如果你忽视了这种可能,无疑是也而协调于未来的道上挖掘了好多“坑”。举例来说:

  string s = "strasse";

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

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

最安全之行是连连为Equals方法提供一个comparisonType的参数。

下面是有些主干的指导标准:

  • 当于用户输入的字符串或者以字符串比较结实展示给用户时时,使用本地化的可比(CurrentCulture
    或者CurrentCultureIgnoreCase)。
  • 当用于程序设计之较字符串时,使用初之于(Ordinal 或者
    OrdinalIgnoreCase)
  • InvariantCulture和InvariantCultureIgnoreCase一般并无动,除非在受限的田地之下,因为本来之比平常效率又胜。如果跟本土文化相关的较是必需的,它当给实践成基于即的知识要其它一样种异常文化的较。

除此以外,对Equals
方法吧,字符串也常见提供了Compare方法,可以供字符串的相对顺序信息一旦不光测试是否当。这个方式可死好适用于<,
<=, >和>= 运算符,对上述议论同样适用。


泛误区 #4: 使用迭代式 (而未是声明式)的报告词去操作集合

在C#
3.0中,LINQ的引入改变了咱们往本着聚集对象的询问以及修改操作。从这以后,你应该为此LINQ去操作集合,而无是经迭代之不二法门。

一些C#的程序员甚至都非理解LINQ的存,好以匪懂得之人数正在慢慢回落。但是还时有发生来人误以为LINQ只所以在数据库询问中,因为LINQ的要紧字与SQL语句其实是无限像了。

尽管数据库的查询操作是LINQ的一个雅突出的用,但是她同样好应用为各种可枚举的汇对象。(如:任何实现了IEnumerable接口的目标)。举例来说,如果你生一个Account类型的累组,不要写成下面这样:

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

卿而这么勾画:

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

则当时是一个那个简短的例子,在微情况下,一个单纯的LINQ语词可以随心所欲地更迭掉你代码中一个迭代循环(或嵌套循环)里的几十漫长语句。更少之代码通常意味着有Bug的会吗会另行少地给引入。然而,记住,在性质方面或者要衡量一下。在性特别关键之情景,尤其是若的迭代代码能够对你的成团进行假设时,LINQ做不顶,所以毫无疑问要是当马上半栽方式中比较一下特性。


#5时常表现错:在LINQ语词之中无考虑底层对象

对于拍卖抽象操纵集合任务,LINQ无疑是极大的。无论他们是以内存的对象,数据库表,或者XML文档。在这么一个周世界中,你无待知道底层对象。然而要我们生活于一个到家世界中间可能会见带动错误。事实上,当以准的同等数量达推行时,如果该多少碰巧在一个不一之格式之中相同的,LINQ语句能回来不同之结果

譬如,请考虑下的说话:

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

想像一下,该目标某的账号会来啊。状态等“Active”(注意大写A)?

好吧,如果myaccout是Dbset的目标。(默认设置了不同界别轻重缓急写的安排),where表达式仍会配合该因素。然而,如果myaccout是于内存阵列之中,那么其以未兼容,因此拿发生不同的毕竟的结果。

相当一会,在咱们事先讨论了之字符串比较中, 我们看见 ==
操作符扮演的角色就是是略的可比. 所以,为什么在这个标准下, ==
表现来之凡另外的一个花样呢 ?

答案是,当于LINQ语词被的根基对象都引用到SQL表中的数量(如与于这事例中,在实体框架为DbSet的目标的情形下),该语句被撤换成一个T-SQL语句。然后以的T-SQL的条条框框,而未是C#的规则,所以于上述情况下之比了是免分轻重缓急写的。

一般情况下,即使LINQ是一个便民的与同等的艺术来询问对象的集结,在切切实实中公还用了解你的讲话是否会见于翻成什么,来确保您的代码的一言一行拿设预期运行。


广错误 #6:对扩大方法感到困惑或为它的款式诈骗

似乎先前事关的,LINQ状态依赖让IEnumerable接口的实现目标,比如,下面的简短函数会商量帐户汇中之帐户余额:

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

当点的代码中,myAccounts参数的品种为声称也IEnumerable<Account>,myAccounts引用了一个Sum
方法 (C# 使用类的 “dot notation”
引用方法还是接口中的接近),我们想在IEnumerable接口中定义一个Sum()方法。但是,IEnumerable没有为Sum方法供任何引用并且只有如下所著之简洁定义:

 public interface IEnumerable<out T> : IEnumerable 
 {
      IEnumerator<T> GetEnumerator();
 }

可Sum方法应该定义及哪里?C#举凡强类型的言语,因此只要Sum方法的援是无效的,C#编译器会对那个报错。我们理解她要在,但是该当何吧?此外,LINQ提供的供应查询和集合结果有办法以哪定义为?

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

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

 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);      ...
    }
 }

恢宏方法的显著特点是第一个形参前的this修饰符。这就算是编译器知道其是一个扩大方法的“奥妙”。它所修饰的参数的门类(这个事例中之IEnumerable)说明是看似或接口将显示实现了这艺术。

(另外索要指出的凡,定义扩展方法的IEnumerable接口和Enumerable类的名字里的相似性没什么奇怪之。这种相似性只是随意的风骨选择。)

晓了这或多或少,我们得望上面介绍的sumAccounts方法能因下面的方式实现:

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

其实我们兴许早已这么实现了是措施,而未是咨询啊而发恢宏方法。扩展方法本身就是C#的一个便宜你随便需继续、重新编译或者涂改原始代码就足以吃业已怀的以列“添加”方法的不二法门。

扩大方法通过以文件开始添加using
[namespace];引入到作用域。你得知道你若摸的恢弘方法所在的讳空间。如果你了解您而物色的凡呀,这点非常轻。

当C#编译器碰到一个靶的实例调用了一个措施,并且其当这目标的好像中检索不至死方式,它就会见尝试当作用域中享有的扩大方法里搜寻一个匹配所要求的类和法签名的。如果找到了,它就是管实例的援当做第一单参数传被好扩展方法,然后要生另参数的说话,再管它们依次传入扩展方法。(如果C#编译器没有当作用域中找到相应的恢宏方法,它见面抛措。)

对C#编译器来说,扩展方法是单“语法糖”,使我们能管代码写得再鲜明,更便于维护(多数情况下)。显然,前提是您明白其的用法,否则,它会于容易给人迷惑,尤其是均等开始。

行使扩展方法真的发生优势,但为会见于那些对她不打听或认识不正确的开发者头疼,浪费时间。尤其是在圈以线示例代码,或者其他已经写好之代码的时节。当这些代码有编译错误(因为她调用了那些显然并未当为调用类型中定义的章程),一般的同情是考虑代码是否采取为所引述类库的任何版本,甚至是不同之类库。很多光阴会给消费在寻觅新本子,或者吃当“丢失”的类库上。

当扩展方法的名字与好像中定义之法门的讳如出一辙,只是以点子签名上产生轻区别的早晚,甚至那些耳熟能详扩展方法的开发者也偶尔犯上面的荒唐。很多岁月会见吃消费在搜索“不有”的拼写错误上。

在C#受到,用扩展方法变得尤其流行。除了LINQ,在另外两单出自微软今于大规模采取的类库Unity
Application Block和Web API
framework中,也使了扩大方法,而且还有许多其他的。框架进一步新,用扩展方法的可能越怪。

自然,你啊得描绘你协调的扩充方法。但是得意识及虽然扩大方法看起来与另实例方法一致让调用,但随即其实只有是幻觉。事实上,扩展方法不克顾所推广展类的私和保护成员,所以她不克吃当传统延续的替代品。


普遍错误 #7: 对手头上之任务采取不当的聚众类型

C#提供了汪洋底集合类型的对象,下面就排有了内部的等同有:

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

然在稍微情况下,有无比多的挑三拣四跟无足够的选项同一糟糕,集合类型为是这么。数量多之精选余地肯定得保是公的工作正常运转。但是若不过好或消费有时日提前查找并问询一下凑类型,以便选择一个极度适合您得的汇类型。这最终会要您的次序性能再好,减少失误的或许。

若有一个汇聚指定的要素类型(如string或bit)和汝方操作的同等,你顶好优先选项使用她。当指定相应的素类型时,这种集的频率又强。

以采取好C#蒙之门类安全,你尽好选使用一个泛型接口,而非是行使非泛型的假说。泛型接口中的元素类型是公以以宣称对象时指定的路,而无泛型中的元素是object类型。当以一个非泛型的接口时,C#的编译器不能够对君的代码进行项目检查。同样,当您于操作原生类型的聚集时,使用非泛型的接口会招C#对这些项目进行反复之装箱(boxing)和拆箱(unboxing)操作。和采取指定了当类型的泛型集合相比,这会带动好显然的性能影响。

外一个广的圈套是好去贯彻一个聚类型。这并无是说永远不要这样做,你得由此下还是扩展.NET提供的组成部分为大面积采用的聚合类型来节省大量底流年,而未是错过还过去轮子。
特别是,C#的C5 Generic Collection Library
和CLI提供了不少附加的汇类型,像持久化树形数据结构,基于堆的先期级列,哈希索引的数组列表,链表等以及再多。


广错误#8:遗漏资源自由

CLR
托管环境去了排泄物回收器的角色,所以若无待显式释放已创建对象所占有的内存。事实上,你吧无可知显式释放。C#着并未和C++
delete对应的运算符或者跟C语言中free()函数对应之章程。但就并无代表你可忽略所有的施用过的目标。许多目标类型封装了很多别类型的系统资源(例如,磁盘文件,数据连接,网络端口等等)。保持这些资源使用状态会重耗尽系统的资源,削弱性能并且最终促成程序出错。

尽管所有C#的好像中还定义了析构方法,但是销毁对象(C#备受为叫做终结器)可能是的题目是你不确定她于上吃调用。他们以未来一个请勿确定的工夫吃垃圾回收器调用(一个异步的线程,此举可能引发额外的面世)。试图避免这种由垃圾回收器中GC.Collect()方法所施加的要挟限制并非同一种植好之编程实践,因为可能以废品回收线程试图回收适宜回收的靶子时,在不可预知的光阴外造成线程阻塞。

就并代表最不用因此终结器,显式释放资源并无见面造成中的另外一个名堂。当你打开一个文书、网络端口或者数额连接时,当你不再采取这些资源时,你应该尽快的显式释放这些资源。

资源泄露几乎以享有的环境遭受还见面抓住关注。但是,C#供了同等种植健康的机制使资源的利用变得简单。如果成立运用,可以大大减少泄露出现的机率。NET
framework定义了一个IDisposable接口,仅由一个Dispose()构成。任何实现IDisposable的接口的靶子还见面以对象生命周期结束调用Dispose()方法。调用结果肯定以决定性的放走占用的资源。

一经在一个代码段遭遇开创并释放一个靶,却遗忘调用Dispose()方法,这是不行原谅的,因此C#供了using语句以确保无代码以什么样的计退出,Dispose()方法还见面于调用(不管是很,return语句,或者略的代码段竣工)。这个using和事先提到的于文书开始用来引入名字空间的一律。它发另外一个广大C#开发者都未曾意识的,完全不相干的目的,也即是管代码退出时,对象的Dispose()方法为调用:

  using (FileStream myFile = File.OpenRead("foo.txt")) {
    myFile.Read(buffer, 0, 100);
  }

每当地方示例中行使using语句,你不怕足以规定myFile.Dispose()方法会在文书使用了之后吃这调用,不管Read()方法有没有发生撇下大。


常见错误 #9: 回避异常

C#以运作时为会见强制进行路检查。相对于诸如C++这样见面让左的类型转换赋一个随机值的语言来说,C#及时可以假设你再次快之找到出错的职。然而,程序员再同不善无视了C#的即无异于表征。由于C#提供了零星种植档次检查的计,一种会丢来异常,而其他一样种植则未会见,这万分可能会见如她们不见进这个“坑”里。有些程序员倾向于回避异常,并且认为不写
try/catch 语句子可以节约一些代码。

譬如,下面演示了C#未遭开展展示类型转换的少栽不同的道:

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

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

杀鲜明,如果不对方法2返的结果开展判断的话,最终老可能会见生出一个
NullReferenceException
的不行,这或许会见油然而生于聊晚数的当儿,这使得问题再度难追踪。对比的话,方法1碰头就抛弃来一个
InvalidCastExceptionmaking,这样,问题之源于就是很显著了。

除此以外,即使你了解要对方法2的回值进行判断,如果您意识价值吗空,接下去你晤面怎么开?在这个法中告知错误合适呢?如果类型转换失败了卿还生任何的法去品尝吗?如果没有的话,那么抛出这个好是唯一正确的选,并且大的丢来点离开那发碰更接近更好。

脚的例证演示了其他同组广泛的主意,一栽会丢来老,而其他一样种则无会见:

  int.Parse();     // 如果参数无法解析会抛出异常
  int.TryParse();  // 返回bool值表示解析是否成功

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

微程序员认为“异常有害”,所以她们自然而然的认为无遏来十分的次第显得越来越“高大上”。虽然于一些情况下,这种观点是不利的,但是这种看法并无适用于拥有的情状。

选个有血有肉的例证,某些情况下当好出时,你闹其它一个可选的措施(如,默认值),那么,选用不丢来大的方式是一个于好的选。在这种情形下,你顶相仿下面这样写:

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

假如未是这样:

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

不过,这并无说明 TryParse
方法还好。某些情况下适合,某些情况下则非适合。这就算是干什么发生一定量种办法供我们选取了。根据你的具体情况选择适用的法子,并切记,作为一个开发者,异常是截然可以成为您的爱侣之。


常见错误 #10: 累积编译器警告而无处理

夫似是而非并无是C#所特有的,但是于C#中这种状态倒是于多,尤其是从C#编译器弃用了从严的型检查下。

警戒的出现是来由之。所有C#的编译错误都标明你的代码有毛病,同样,一些警示吗是这么。这两者之间的区分在于,对于警告的话,编译器可以以卿代码的指令工作,但是,编译器发现而的代码来同样接触多少题目,很有或会见使你的代码不能够随卿的料运行。

一个大的例证是,你改改了你的代码,并移除了对某些变量的利用,但是,你忘掉了移除了该变量的声明。程序可以很好的运作,但是编译器会提示有未利用的变量。程序可以老好的运作使得一些程序员不错过修复警告。更有甚者,有些程序员很好之采取了Visual
Studio中“错误列表”窗口的隐藏警告的功能,很容易的即将警告过滤了,以便专注让左。不用多长时间,就会见攒一堆积警告,这些警告都被“惬意”的忽视了(更不好底是,隐藏掉了)。

可,如果你不经意掉这等同像样的警告,类似于下是例子迟早会冒出在你的代码中。

  class Account 
  {

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

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

  }

重新长使用了编辑器的智能感知的效能,这种似是而非就格外有或发生。

现行,你的代码中发出矣一个严重的左(但是编译器只是出口了一个警告,其原因既说明了),这会浪费你大量底时日去搜寻这错,具体情况由而的次序复杂程度决定。如果你同样开始便注意到了此警示,你一味待5秒钟就可修改掉,从而避免这题目。

牢记,如果您仔细看的语句,你晤面意识,C#编译器给了卿不少关于您程序健壮性的中的音信。不要大意警告。你才待花费几秒钟的流年尽管可修复它们,当出现的时候便失修复它,这得吧你省成千上万时刻。试着也祥和造同样种植“洁癖”,让Visual
Studio 的“错误窗口”一直显示“0误,
0警告”,一旦出现警示便感觉不舒适,然后马上把警告修复掉。

理所当然矣,任何规则都发生不同。所以,有些上,虽然你的代码在编译器看来是小问题的,但是这正是你想使的。在这种异常少见的情景下,你最好好利用
#pragma warning disable [warning id]
把吸引警告的代码包裹起来,而且只是包警告ID对应的代码。这会都只会制止对应的警告,所以当有新的告诫来的时刻,你要会知道的。.
总结

C#举凡千篇一律帮派强大的同时颇灵敏的言语,它发为数不少体制与语言专业来家喻户晓的加强而的生产力。和其余语言一样,如果对它能力的问询少,这老可能会见让你带阻碍,而休是便宜。正而一句子谚语所说之那样“knowing
enough to be
dangerous”(译者注:意思是自以为已经了解足够了,可以做某事了,但实在不是)。

熟悉C#的一些最主要之轻微的处在,像本文中所涉嫌的那些(但无压这些),可以协助我们又好的错过下语言,从而避免有广大的钩。