1.开辟流程
递次的Bug与瑕疵每每涌现于开辟流程当中。只要对东西善加运用,就有助于在你宣布递次之前便将题目发明,或避开这些题目。
规范化代码誊写
规范化代码誊写可以使代码越发易于庇护,尤其是在代码由多个开辟者或团队举行开辟与庇护时,这一长处越发凸起。罕见的强迫代码范例化的东西有:FxCop、StyleCop和ReSharper。
开辟者语:在掩饰毛病之前请细致地思索这些毛病,而且去剖析效果。不要愿望依托这些东西来在代码中寻觅毛病,因为效果可以和你的与其相去甚远。
代码检察
检察代码与同伴编程都是很罕见的演习,比方开辟者锐意去检察他人誊写的代码。而其他人很愿望发明代码开辟者的一些bug,比方编码毛病或许实行毛病。
检察代码是一种很有代价的演习,因为很依赖于人工操纵,因而很难被量化,准确度也不够令人满意。
静态剖析
静态剖析不须要你去运转代码,你没必要编写测试案例就可以找出一些代码不范例的处所,或许是一些瑕疵的存在。这是一种非常有效地寻觅题目标体式格局,然则你须要有一个不会有太多误报题目标东西。C#常常运用的静态剖析东西有Coverity,CAT,NET,Visual Studio Code Analysis。
动态剖析
在你运转代码的时刻,动态剖析东西可以帮你找出这些毛病:平安破绽,机能与并发性题目。这类要领是在实行时代的环境下举行剖析,正因如此,其有效性便受制于代码庞杂度。Visual Studio供应了包括Concurrency Visualizer, IntelliTrace, and Profiling Tools在内的大批动态剖析东西。
治理者/团队领导语:开辟实践是演习躲避罕见圈套的最好要领。同时也要注意测试东西是不是相符你的需求。只管让你团队的代码诊断程度处于可控的局限内。
测试
测试的体式格局多种多样:单元测试,体系集成测试,机能测试,渗入测试等等。在开辟阶段,绝大多数的测试案例是由开辟者或测试职员来完成编写,使递次可以满足需求。
测试只在运转准确的代码时才会有效。在举行功用测试的时刻,它还可以用来应战开辟者的研发与庇护速率。
开辟最好实践
东西的挑选上多花点时候,用准确的东西去处置惩罚你体贴的题目,不要为开辟者增添分外的事情。让剖析东西与测试自动流畅地运转起往来来往寻觅题目,然则要保证代码的思想依然清晰地留在开辟者的思想当中。
只管快地定位诊断出来的题目所在位置(不管是经由历程静态剖析照样测试取得的毛病,比方编译正告,规范违例,题目检测等)。假如刚出来的题目因为“不体贴”而去疏忽它,致使该题目厥后很难找到,那末就会给代码审视事情者增添很大的事情量,而且还要祷告他们不会因而焦躁。
请吸收这些有效的发起,让本身代码的质量,平安性,可庇护性取得提拔,同时也提拔开辟者们的研发才能、谐和才能,以及提拔宣布代码的可展望性。
目标 | 东西 | 影响 |
一致性,可庇护性 | 规范化代码誊写,静态剖析,代码检察 | 间距一致,定名规范,优越的可读花样,都邑让开辟者更轻易编写与庇护代码。 |
准确性 | 代码检察,静态剖析,动态剖析,测试 | 代码不只是须要语法准确,还须要以开辟者的思想来满足软件需求。 |
功用性 | 测试 | 测试可以考证大多数的需求是不是取得满足:准确性,可拓展性,鲁棒性以及平安性。 |
平安性 | 规范化代码誊写,代码检察,静态剖析,动态剖析,测试 | 平安性是一个庞杂的题目,任何一个小的破绽都是潜伏的要挟。 |
开辟者研发才能 | 规范化代码誊写,静态剖析,测试 | 开辟者在东西的协助下会很疾速地改正毛病。 |
宣布可展望性 | 规范化代码誊写,代码检察,静态剖析,动态剖析,测试 | 流线型后期阶段的运动、最小化毛病定位轮回,都可以让题目发明的更早。 |
2.范例的圈套
C#的一个主要的长处就是其天真的范例体系,而平安的范例可以协助我们更早地找到毛病。经由历程强迫实行严厉的范例划定规矩,编译器可以协助你保持优越的代码誊写习气。在这一方面,C#言语与.NET框架为我们供应了大批的范例,以顺应绝大多数的需求。虽然许多开辟者对平常的范例有着优越的邃晓,而且也晓得用户的需求,然则一些误会与误用依然存在。
更多关于.NTE框架类库的信息请参阅MSDN library。
邃晓并运用规范接口
特定的接口触及到常常运用的C#特征。比方,IDiposable许可运用罕见的资本治理言语,比方关键词“using”。优越地邃晓接口可以协助你誊写通畅的C#代码,而且更轻易于庇护。
防备运用ICloneable接口——开辟者从来没搞清晰一个被复制的对象究竟是深拷贝照样浅拷贝。因为仍没有一种对复制对象操纵是不是准确的规范评判,因而也就没办法有意义地去将接口作为一个contract去运用。
组织体
只管防备向组织体中举行写入,将它们视为一种稳固的对象以防备杂沓。在像多线程这类场景下举行内存同享,会变得更平安。我们对组织体采纳的要领是,在竖立组织体时对其举行初始化操纵,假如须要转变其数据,那末发起生成一个新的实体。
准确邃晓哪些规范范例/要领是不可变,而且可返回新的值(比方串,日期),用这些来替代那些易变对象(如List.Enumerator)。
字符串
字符串的值可以为空,所以可以在适宜的时刻运用一些比较轻易的功用。值推断(s.Length==0)时可以会涌现NullReferenceException毛病,而String.IsNullOrEmpty(s)和String.IsNullOrWhitespace(s)可以很好地运用null。
标记罗列
罗列范例与常量可以使代码越发易于浏览,经由历程运用标识符替代幻数,可以表现出值的意义。
假如你须要生成大批的罗列范例,那末带有标记的罗列范例是一种越发简朴的挑选:
[Flag] public enum Tag { None =0x0, Tip =0x1, Example=0x2 }
下面这类要领可以让你在一个snippet中运用多重标记:
snippet.Tag = Tag.Tip | Tag.Example
这类要领有利于数据的封装,因而你也没必要忧郁在运用Tag property getter时有内部鸠合信息泄漏。
Equality comparisons(相称性比较)
有以下两种范例的相称性:
1.援用相称性,即两种援用都指向统一个对象。
2.数值相称性,即两个差异的援用对象可以视为相称的。
除此以外,C#还供应了许多相称性的测试要领。最罕见的要领以下:
==与!=操纵
由对象的虚继承等值法
静态Object.Equal法
IEquatable<T>接口等值法
静态Object.ReferenceEquals法
偶然刻很难弄清晰运用援用或值相称性的目标。想进一步弄邃晓这些,而且让你的事情做得更好,请参阅:
MSDNhttp://msdn.microsoft.com/en-us/library/dd183752.aspx
假如你想要掩盖某个东西的时刻,不要忘了MSDN上为我们供应的诸如IEquatable<T>, GetHashCode()之类的东西。
注意无范例容器在重载方面的影响,可以斟酌运用“myArrayList[0] == myString”这一要领。数组元素是编译阶段范例的“对象”,因而援用相称机可以运用。虽然C#会向你提示这些潜伏的毛病,然则在编译历程当中,unexpected reference equality在某些状况下不会被提示。
3.类的圈套
封装你的数据
类在恰当治理数据方面起很大的作用。鉴于机能上的一些缘由,类老是缓存部份效果,或许是在内部数据的一致性上做出一些假定。使数据权限公然的话会在肯定程度上让你去缓存,或许是作出假定,而这些操纵是经由历程对机能、平安性、并发性的潜伏影响表现出来的。比方暴露像泛型鸠合、数组之类的易变成员项,可以让用户跳过你而直接举行组织体的修正。
属性
除了可以经由历程access modifiers掌握对象以外,属性还可以让你很精确地掌控用户与你的对象之间举行了什么交互。迥殊要指出的是,属性还可以让你相识到读写的具体状况。
属机能在经由历程存储逻辑将数据覆写进getters与setters的时刻协助你竖立一个稳固的API,或是供应一个数据的绑定资本。
永久不要让属性getter涌现非常,而且也要防备修正对象状况。这是一种对要领的需求,而不是属性的getter。
更多有关属性的信息,请参阅MSDN:
http://msdn.microsoft.com/en-us/library/ms229006(v=vs.120).aspx
同时也要注意getter的一些副作用。开辟者也习气于将成员体的存取视为一种罕见的操纵,因而他们在代码检察的时刻也常常无视那些副作用。
对象初始化
你可以为一个新竖立的对象依据它竖立的表达情势给予属性。比方为Foo与Bar属性竖立一个新的具有给定值的C类对象:
new C {Foo=blah, Bar=blam}
你也可以生成一个具有特定属性称号的匿名范例的实体:
var myAwesomeObject = new {Name=”Foo”, Size=10};
初始化历程在组织函数体之前运转,因而须要保证在输入至组织函数之前,将这一域给初始化。因为组织函数还没有运转,所以目标域的初始化可以不管如何都不触及“this”。
过渡范例细化的输入参数
为了使一些迥殊要领越发轻易掌握,最好在你运用的要领当中运用起码的特定范例。比方在一种要领中运用 List<Bar>举行迭代:
public void Foo(List<Bar> bars) { foreach(var b in bars) { // do something with the bar... } }
关于其他IEnumerable<Bar>集来讲,运用这类要领的表现越发精彩一些,然则关于特定的参数List<Bar>来讲,我们更须要使集以表的情势表现。只管少地拔取特定的范例(诸如IEnumerable<T>, ICollection<T>此类)以保证你的要领效力的最大化。
4.泛型
泛型是一种在定义自力范例组织体与想象算法上一种非常有力的东西,它可以强迫范例变得平安。
用像List<T>如许的泛型集来替代数组列表这类无范例集,既可以提拔平安性,又可以提拔机能。
在运用泛型时,我们可以用关键词“default”来为范例猎取缺省值(这些缺省值不可以硬编码写进implementation)。迥殊要指出的是,数字范例的缺省值是o,援用范例与空范例的缺省值为null。
T t = default(T);
5.范例转换
范例转换有两种形式。其一显式转换必需由开辟者挪用,另一隐式转换是基于环境下运用于编译器的。
常量o可由隐式转换至罗列型数据。当你尝试挪用含有数字的要领时,可以将这些数据转换成罗列范例。
范例转换 | 形貌 |
Tree tree = (Tree)obj; | 这类要领可以在对象是树范例时运用;假如对象不是树,可以会涌现InvalidCast非常。 |
Tree tree = obj as Tree; | 这类要领你可以在展望对象是不是为树时运用。假如对象不是树,那末会给树赋值null。你可以用“as”的转换,然后找到null值的返回处,再举行处置惩罚。因为它须要有前提处置惩罚的返回值,因而记着只在须要的时刻才去用这类转换。这类分外的代码可以会形成一些bug,还可以会下降代码的可读性。 |
转换一般意味着以下两件事之一:
1.RuntimeType的表现可比编译器所表现出来的迥殊的多,Cast转换敕令编译器将这类表达视为一种更迥殊的范例。假如你的想象不准确的话,那末编译器会向你输出一个非常。比方:将对象转换成串。
2.有一种完全差异的范例的值,与Expression的值有关。Cast敕令编译器生成代码去与该值相干联,或许是在没有值的状况下报出一个非常。比方:将double范例转换成int范例。
以上两种范例的Cast都有着风险。第一种Cast向我们提出了一个题目:“为何开辟者能很清晰地晓得题目,而编译器为何不能?”假如你处于这个状况当中,你可以去尝试转变递次让编译器可以顺遂地推理出准确的范例。假如你以为一个对象的runtime type是比compile time type还要迥殊的范例,你就可以用“as”或许“is”操纵。
第二种cast也提出了一个题目:“为何不在第一步就对目标数据范例举行操纵?”假如你须要int范例的效果,那末用int会比double更有意义一些。
猎取分外的信息请参阅:
http://blogs.msdn.com/b/ericlippert/archive/tags/cast+operator/
在某些状况下显式转换是一种准确的挑选,它可以进步代码可浏览性与debug才能,还可以在采纳适宜的操纵的状况下进步测试才能。
6.非常
非常并非condition
非常不应当常涌现在递次流程中。它们代表着开辟者所不肯看到的运转环境,而这些极可以没法修复。假如你希冀取得一个可掌握的环境,那末主动去搜检环境会比守候题目标涌现要好很多。
运用TryParse()要领可以很轻易地将花样化的串转换成数字。不管是不是剖析胜利,它都邑返回一个布尔型效果,这要比纯真返回非常要好许多。
注意运用exception handling scope
写代码时注意catch与finally块的运用。因为这些不肯望取得的非常,掌握可以进入这些块中。那些你希冀的已实行的代码可以会因为非常而跳过。如:
Frobber originalFrobber = null; try { originalFrobber = this.GetCurrentFrobber(); this.UseTemporaryFrobber(); this.frobSomeBlobs(); } finally { this.ResetFrobber(originalFrobber); }
假如GetCurrentFrobber()报出了一个非常,那末当finally blocks被实行时originalFrobber的值依然为空。假如GetCurrentFrobber不能被扬弃,那末为何其内部是一个try block?
明智地处置惩罚非常
要注意有针对性地处置惩罚你的目标非常,而且只去处置惩罚目标代码当中的非常部份。只管不要去处置惩罚统统非常,或许是根类非常,除非你的目标是纪录并从新处置惩罚这些非常。某些非常会使运用处于一种靠近崩溃的状况,但这也比没法修复要好很多。有些试图修复代码的操纵可以会误使状况变得更蹩脚。
关于致命的非常都有一些纤细的差异,迥殊是注意finally blocks的实行,可以影响到非常的平安与调试。更多信息请参阅:
http://incrediblejourneysintotheknown.blogspot.com/2009/02/fatal-exceptions-and-why-vbnet-has.html
运用一款顶级的非常处置惩罚器去平安地处置惩罚非常状况,而且会将debug的一些题目信息暴露出来。运用catch块会比较平安地定位那些迥殊的状况,从而平安地处置惩罚这些题目,再将一些题目留给顶级的非常处置惩罚器去处置惩罚。
假如你发明了一个非常,请做些什么去处置惩罚它,而不要去将这个题目放置。放置只会使题目越发庞杂,更难以处置惩罚。
将非常包括至一个自定义非常中,对面向大众API的代码迥殊有效。非常是可视界面要领的一部份,它也被参数与返回值所掌握。但这类扩散了许多非常的要领关于代码的鲁棒性与可庇护性的处置惩罚来讲非常贫苦。
抛出(Throw)与继承抛出(ReThrow)非常
假如你愿望在更高条理上处置惩罚caught非常,那末就保持原非常状况,而且栈就是一个很好的debug要领。但须要注意保持好debug与平安斟酌的均衡。
好的挑选包括简朴地将非常继承抛出:
Throw;
或许将非常视为内部非常从新抛出:
抛出一个新CustomException;
不要显式从新抛出相似于如许的caught非常:
Throw e;
如许的话会将非常的处置惩罚恢复至初始状况,而且障碍debug。
有些非常发作于你代码的运转环境以外。与其运用caught块,你可以更须要向目标当中增加如ThreadException或UnhandledException之类的处置惩罚器。比方,Windows窗体非常并非涌现于窗体处置惩罚线程环境当中的。
原子性(数据完全性)
万万不要让非常影响到你数据模型的完全性。你须要保证你的对象处于比较稳固的状况当中——如许一来任何由类的实行的操纵都不会涌现违例。不然,经由历程“恢复”这一手腕会使你的代码变得越发让人不解,也轻易形成进一步的破坏。
斟酌几种修正私有域递次的要领。假如在修正递次的历程当中涌现了非常,那末你的对象可以并不处于不法状况下。尝试在实际更新域之前往取得新的值,如许你就可以在非常平安治理下,一般地更新你的域。
对特定范例的值——包括布尔型,32bit或许更小的数据范例与援用型——举行可变量的分派,确保可所以原子型。没有什么保证是给一些大型数据(double,long,decimal)运用的。可以多斟酌这个:在同享多线程的变量时,多运用lock statements。
7.事宜
事宜与托付配合供应了一种关于类的要领,这类要领在有迥殊的事变发作时向用户举行提示。托付事宜的值在事宜发作时应被挪用。事宜就像是托付范例的域,当对象生成时,其自动初始化为null。
事宜也像值为“组播”的域。这也就是说,一种托付可以顺次挪用其他托付。你可以将一个托付分派给一个事宜,你也可以经由历程相似-=于+=如许的操纵来掌握事宜。
注意资本合作
假如一个事宜被多个线程所同享,另一个线程就有可以在你搜检是不是为null以后,在挪用其之前而消灭统统的用户信息——并抛出一个NullReferenceException。
关于此类题目标规范处置惩罚要领是竖立一个该事宜的副本,用于测试与挪用。你依然须要注意的是,假如托付没有被准确挪用的话,那末在其他线程里被移除的用户依然可以继承操纵。你也可以用某种要领将操纵按递次锁定,以防备一些题目。
public event EventHandler SomethingHappened; private void OnSomethingHappened() { // The event is null until somebody hooks up to it // Create our own copy of the event to protect against another thread removing our subscribers EventHandler handler = SomethingHappened; if (handler != null) handler(this,new EventArgs()); }
更多关于事宜与合作的信息请参阅:
http://blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx
不要遗忘将事宜处置惩罚器Unhook
运用一种事宜处置惩罚器为事宜资本生成一个由处置惩罚器的资本对象到吸收对象的援用,可以庇护吸收端的garbage collection。
恰当的unhook处置惩罚器可以确保你没必要因托付不再事情而去挪用它浪费时候,也不会使内存存储无用托付与不可援用的对象。
8.属性
属性供应了一种向递次集、类与其信息属性中注入元数据的要领。它们经常常运用来供应信息给代码的消费者——比方debugger、框架测试、运用——经由历程反射这一体式格局。你也可以向你的用户定义属性,或是运用预定义属性,详见下表:
属性 | 运用对象 | 目标 |
DebuggerDisplay | Debugger | Debugger display 花样 |
InternalsVisibleTo | Member access | 运用特定类来暴露内部成员去指定其他的类。基于此要领,测试要领可以用来庇护成员,而且persistence层可以用一些迥殊的隐藏要领。 |
DefaultValue | Properties | 为属性指定一个缺省值 |
肯定要对DebuggerStepThrough多注重几分——不然它会在这个要领运用的处所让寻觅bug变得好不轻易,你也会因而而跳过某步或是推倒而重做它。
9.Debug
Debug是在开辟历程当中必不可少的部份。除了使运转环境不透明的部份变得可视化以外,debugger也可以侵入运转环境,而且假如不运用debugger的话会致使运用递次变现有所差异。
使非常栈可视化
为了视察当前框架非常状况,你可以将“$exception”这一表达增加进Visual Studio Watch窗口。这类变量包括了当前非常状况,相似于你在catch block中所瞥见的,但个中不包括在debugger中瞥见的不是代码中的真正存在的非常。
注意接见器的副作用
假如你的属性有副作用,那末斟酌你是不是应运用特征或许是debugger设置去防备debugger自动地挪用getter。比方,你的类可以有如许一个属性:
private int remainingAccesses = 10; private string meteredData; public string MeteredData { get { if (remainingAccesses-- > 0) return meteredData; return null; } }
你第一次在debugger中瞥见这个对象时,remainingAccesses会取得一个值为10的整型变量,而且MeteredData为null。但是假如你hover完毕了remainingAccesses,你会发明它的值会变成9.如许一来debugger的属性值表现转变了你的对象的状况。
10.机能优化
早做设计,不停监测,后做优化
在想象阶段,制订切实可行的目标。在开辟阶段,专注于代码的准确性要比去做微调解有意义的多。关于你的目标,你要在开辟历程当中多举行监测。只须要在你没有到达预期的目标的时刻,你才应当去花时候对递次做一个调解。
请记着用适宜的东西来确保机能的经验性丈量,而且使测试处于如许一种环境当中:可重复屡次测试,而且测试历程只管与实际当顶用户的运用习气一致。
当你对机能举行测试的时刻,肯定要注意你真正所体贴的测试目标是什么。在举行某一项功用的测试时,你的测试有无包括这项功用的挪用或许是回路组织的开支?
我们都听说过许多比他人做得快许多的项目神话,不要自觉置信这些,实验与测试才是着实的东西。
因为CLR优化的缘由,偶然刻看起来效力不高的代码可以会比看起来效力高的代码运转的更快。比方,CLR优化轮回掩盖了一个完全的数组,以防备在不可见的per-element局限里的搜检。开辟者常常在轮回一个数组之前先盘算一下它的长度:
int[] a_val = int[4000]; int len = a_val.Length; for (int i = 0; i < len; i++) a_val[i] = i;
经由历程将长度存储进一个变量当中,CLR会不去辨认这一部份,而且跳过优化。然则偶然手动优化会反人类地致使更蹩脚的机能表现。
组织字符串
假如你盘算将大批的字符串举行衔接,可以运用System.Text.StringBuilder来防备生成大批的暂时字符串。
对鸠合运用批量处置惩罚
假如你盘算生成并填满鸠合中已知的大批数据,因为再分派的存在,可以用保留空间来处置惩罚生成鸠合的机能与资本题目。你可以用AddRange要领来进一步对机能举行优化,以下在List<T>中处置惩罚:
Persons.AddRange(listBox.Items);
11.资本治理
垃圾收集器(garbage collector)可以自动地清算内存。纵然如许,统统被扬弃的资本也须要恰当的处置惩罚——迥殊是那些垃圾收集器不能治理的资本。
资本治理题目标罕见泉源 | |
内存碎片 | 假如没有足够大的一连的虚拟地址存储空间,可以会致使分派失利 |
历程限定 | 历程一般都可以读取内存的统统子集,以及体系可用的资本。 |
资本泄漏 | 垃圾收集器只治理内存,其他资本须要由运用递次准确治理。 |
不稳固资本 | 那些依赖于垃圾收集器与闭幕器(finalizers)的资本在良久没用过的时刻,不可被马上挪用。实际上它们可以永久不可以被挪用。 |
运用try/finally block来确保资本已被合理开释,或是让你的类运用IDisposable,以及更轻易更平安的声明体式格局。
using (StreamReader reader=new StreamReader(file)) { //your code here
在产品代码中防备garbage collector
除了用挪用GC.Collect()滋扰garbage collector以外,也可以斟酌恰当地开释或是扬弃资本。在举行机能测试时,假如你可以负担这类影响带来的效果,你再去运用garbage collector。
防备编写finalizers
与当前一些撒布的流言差异的是,你的类不须要Finalizers,而这只是因为IDisposable的存在!你可以让IDisposable给予你的类在任何已具有的组合实例中挪用Dispose的才能,然则finalizers只能在具有未治理的资本类中运用。
Finalizers主要对交互式Win32位句柄API有很大作用,而且SafeHandle句柄是很轻易运用的。
不要老是想象你的finalizers(老是在finalizer线程上运转的)会很好地与其他对象举行交互。那些其他的对象可以在该历程之前就被停止掉了。
12.并发性
处置惩罚并发性与多线程编程是件庞杂的、难题的事变。在将并发性增加进你的递次之前,请确保你已明白相识你的做的是什么——因为这里面有太多门道了!
多线程软件的状况很难举行展望,比方很轻易产生如合作前提与死锁的题目,而这些题目并非仅仅影响单线程运用。基于这些风险,你应当将多线程视为末了一种手腕。假如不能不运用多线程,只管缩减多线程同时运用内存的需求。假如必需使线程同步,请只管地运用最高品级的同步机制。在最高品级的前提下,包括了这些机制:
Async-await/Task Parallel Library/Lazy<T>
Lock/monitor/AutoResetEvent
Interlocked/Semaphore
可变域与显式barrier
以上的这些很难诠释清晰C#/.NET的庞杂的地方。假如你想开辟一个一般的并发运用,可以去参阅O’Reilly的《Concurrency in C# Cookboo》。
运用Volatile
将一个域标记为“volatile”是一种高等特征,而这类设置也常常被专家所误会。C#的编译器会保证目标域可以被猎取与开释语义,然则被lock的域就不适用于这类状况。假如你不晓得猎取什么,不晓得开释什么语义,以及它们是如何影响CPU条理的优化,那末久防备运用volatile域。取而代之的可以用更高条理的东西,比方Task Parallel Library或是CancellationToken。
线程平安与内置要领
规范库范例常供应使对象线程平安更轻易的要领。比方Dictionary.TryGetValue()。运用此类要领平常可以使你的代码变得越发清新,而且你也没必要忧郁像TOCTOU(time-of-check-time-of-use合作伤害的一种)如许的数据合作。
不要锁住“this”、字符串,或是其他一般public的对象
当运用在多线程环境下的一些类时,多注意lock的运用。锁住字符串常量,或是其他大众对象,会阻挠你锁状况下的封装,还可以会致使死锁。你须要阻挠其他代码锁定在统一运用的对象上,固然你最好的挑选是运用private对象成员项。
13.防备罕见的毛病
Null
滥用null是一种罕见的致使递次毛病的泉源,这类非一般操纵可以会使递次崩溃或是其他的非常。假如你试图猎取一个null的援用,就好像它是某对象的有效援用值(比方经由历程猎取一个属性或是要领),那末在运转时就会抛出一个NullReferenceException。
静态与动态剖析东西可以在你宣布代码之前为你搜检出潜伏的NullReferenceException。在C#当中,援用型为null一般是因为变量没有援用到某个对象而形成的。关于值可为空的范例与援用型来讲,是可以运用null的。比方:Nullable<Int>,空托付,已注销的事宜,“as”转化失利的,以及一些其他的状况。
每一个null援用非常都是一个bug。比拟于找到NullReferenceException这个题目来讲,不如尝试在你运用该对象之前往为null举行测试。如许一来可以使代码更轻易于最小化的try/catch block读取。
当从数据库表中读取数据时,注意缺失值可以示意为DBNull 对象,而不是作为空援用。不要希冀它们表现得像潜伏的空援用一样。
用二进制的数字示意十进制的值
Float与double都可以示意十进制实数,但不能示意二进制实数,而且在存储十进制值的时刻可以在必要时用二进制的近似值存储。从十进制的角度来看,这些二进制的近似值一般都有差异的精度与弃取,偶然在算数操纵当中会致使一些不希冀的效果。因为浮点型运算一般在硬件当中实行,因而硬件前提的不可展望会使这些差异越发庞杂。
在十进制精度很主要的时刻,就要运用十进制了——比方经济方面的盘算。
调解组织
有一种罕见的毛病就是遗忘了组织是值范例,意即其复制与经由历程值通报。比方你可以见过如许的代码:
struct P { public int x; public int y; } void M() { P p = whatever; … p.x = something; … N(p);
遽然某一天,代码庇护职员决定将代码重组成如许:
void M() { P p = whatever; Helper(p); N(p); } void Helper(P p) { … p.x = something;
现在当N(p)在M()中被挪用,p就有了一个毛病的值。挪用Helper(p)通报p的副本,并非援用p,因而在Helper()中的突变便丧失掉了。假如被一般挪用,那末Helper应当通报的是调解过的p的副本。
非预期盘算
C#编译器可以庇护在运算历程当中的常量溢出,但不肯定是盘算值。运用“checked”与“unchecked”两个关键词来标记你想对变量举行什么操纵。
不保留返回值
与组织体差异的是,类是援用范例,而且可以恰当地修正援用对象。但是并非统统的对象要领都可以实际修正援用对象,有一些返回的是一个新的对象。当开辟者挪用后者时,他们须要记着将返回值分派给一个变量,如许才可以运用修正过的对象。在代码检察阶段,这些题目标范例一般会逃过检察而不被发明。像字符串之类的对象,它们是不可变的,因而永久不可以修正这些对象。即便如此,开辟者照样很轻易遗忘这些题目。
比方,看以下 string.Replace()代码:
string label = “My name is Aloysius”; label.Replace(“Aloysius”, “secret”);
这两行代码运转以后会打印出“My name is Aloysius” ,这是因为Raeplace要领并没转变该字符串的值。
不要使迭代器与罗列器失效
注意不要在遍用时去修正鸠合
List<Int> myItems = new List<Int>{20,25,9,14,50}; foreach(int item in myItems) { if (item < 10) { myItems.Remove(item); // iterator is now invalid! // you’ll get an exception on the next iteration
假如你运转了这个代码,那末它一鄙人一项的鸠合中举行轮回,你就会取得一个非常。
准确的处置惩罚要领是运用第二个list去保留你想删除的这一项,然后在你想删除的时刻再遍历这个list:
List<Int> myItems = new List<Int>{20,25,9,14,50}; List<Int> toRemove = new List<Int>(); foreach(int item in myItems) { if (item < 10) { toRemove.Add(item); } } foreach(int item in toRemove) {
假如你用的是C#3.0或更高版本,可以尝试List<T>.RemoveAll:
myInts.RemoveAll(item => (item < 10));
属性称号毛病
在完成属性时,要注意属性的称号和在类当顶用的成员项的名字有很大差异。很轻易在不知情的状况下运用了雷同的称号,而且在属性被猎取的时刻还会触发死轮回。
// The following code will trigger infinite recursion private string name; public string Name { get { return Name; // should reference “name” instead.
在重定名间接属性时一样要警惕。比方:在WPF中绑定的数据将属性称号指定为字符串。偶然无意的转变属性称号,可以会不警惕形成编译器没法处置惩罚的题目。
英文原文:13 Things Every C# Developer Should Know 翻译:码农网
以上就是C#开辟者必需晓得的13件事变的细致内容,更多请关注ki4网别的相干文章!