新昌网站建设,wordpress 股票主题,服装企业网站建设可行性分析,编程自学本文讲述了C#开发人员应该了解到的13件事情#xff0c;希望对C#开发人员有所帮助。 1. 开发过程 开发过程是错误和缺陷开始的地方。使用工具可以帮助你在发布之后#xff0c;解决掉一些问题。 编码标准 遵照编码标准可以编写出更多可维护的代码#xff0c;特别是在由多个开发… 本文讲述了C#开发人员应该了解到的13件事情希望对C#开发人员有所帮助。 1. 开发过程 开发过程是错误和缺陷开始的地方。使用工具可以帮助你在发布之后解决掉一些问题。 编码标准 遵照编码标准可以编写出更多可维护的代码特别是在由多个开发人员或团队编写和维护的代码库中。例如FxCopStyleCop和ReSharper等就是常用的实施编码标准的工具。 开发人员在压缩代码之前请使用工具仔细检查是否违反了标准并且对结果进行分析。使用工具发现的代码路径问题不比你预期的少。 代码审查 代码审查和结对编程是任务开发人员审查他人编写的源代码的常见做法。通过这些方式希望能够检查出作者的错误如编码错误或实现错误。 代码审查是一个很有价值的做法但是它依赖于人类易犯错误所以很难扩展。 静态分析 静态分析工具会在不运行代码的情况下分析代码在不需要编写测试用例的情况下查找违反编码标准或存在缺陷的问题。它们能有效地找到问题但你需要选择出那些能够定位出有价值问题的工具找出有价值的问题。C静态分析工具包括CoverityCAT.NET和Visual Studio代码分析。 动态分析 动态分析工具在运行时分析代码帮助你查找缺陷如安全漏洞性能和并发问题。它分析运行时环境的上下文中的代码因此其有效性受测试工作负载的限制。Visual Studio提供了一些动态分析工具包括并发可视化器IntelliTrace和分析工具。 管理人员/团队领导利用开发最佳实践以避免常见的陷阱。仔细考虑可用的工具以确保它们与你的需求和文化兼容。 测试 有许多类型的测试例如单元测试系统集成测试性能测试渗透测试。在开发阶段大多数测试由开发人员或测试人员编写以验证应用程序是否满足其要求。 测试仅在它们运行正确的代码时有效。在实现功能和测试的同时保持开发速度是具有挑战性的。 开发最佳实践 投入时间来识别和配置工具以便找到你关心的代码问题无需为开发人员带来更多的工作。经常自动运行分析工具和测试以确保开发人员在代码刚写完不久就能定位到问题。 尽快地定位到所有的诊断输出 - 无论是编译器警告标准违例通过静态分析识别的缺陷还是测试失败。如果新的诊断全部是可忽略的那么审查所起的作用就增加了开发人员也不必再为代码问题烦恼。 采用这些最佳实践有助于提高代码的质量安全性和可维护性开发人员的一致性和生产力以及发布的可预测性。 关心工具影响一致性可维护性编码标准静态分析代码审查一致的间距命名和格式化提高了可读性并使开发人员更容易编写和维护代码。正确性代码审查静态分析动态分析测试代码不仅需要在语法上有效而且必须按照开发人员的意图并满足项目需求。功能测试测试验证代码是否满足要求如正确性可扩展性鲁棒性和安全性。安全编码标准代码审查静态分析动态分析测试安全是一个非常复杂的问题; 任何弱点或缺陷都可能被利用。开发人员生产力编码标准静态分析测试当他们有工具来识别错误时开发人员更快地实现代码更改。释放可预测性编码标准代码审查静态分析动态分析测试简化后期活动尽早解决缺陷和问题尽可能缩短修复周期。 2. 类型陷阱 C的一个主要优势是其灵活的类型系统; 类型安全有助于早期发现错误。通过强制实施严格的类型规则编译器能够帮助你保持正确的编码实践。C语言和.NET框架提供了丰富的类型集合以适应最常见的需求。大多数开发人员很好地了解常见的类型及其用途但有一些常见的误解和误用。 有关.NET Framework类库的更多信息可以在MSDN库中找到。 了解和使用标准接口 某些接口涉及常用的C特性。例如IDisposable允许使用常用的资源处理习语例如“using”关键字。理解什么时候使用接口能够使你编写更容易维护的C代码。 避免ICloneable - 设计者从来没有明确拷贝的对象是深拷贝还是浅拷贝。由于没有正确拷贝对象行为的标准也就无法有效的使用这样的接口。 结构 尽量避免写到结构体。将它们视为不可变的能够防止混淆的发生并且在共享内存的场景如多线程应用程序下更安全。相反在创建结构体时使用初始化对象如果需要更改值则创建新的实例。 要了解哪些标准类型/方法是不可变的并返回新值例如stringDateTime和哪些是可变的List.Enumerator。 字符串 字符串可以为null因此在适当时使用起来很方便。等价s.Length 0可能会抛出一个NullReferenceException但是String.IsNullOrEmptys和String.IsNullOrWhitespaces函数能够优雅地处理null。 标记枚举 枚举类型和常量值是能表露出自己含义的标识符用于替换魔术数字以便使得代码更加可读。 如果你发现需要创建枚举的集合标记枚举可能是一个更简单的选择 [Flag]public enum Tag {None 0x0,Tip 0x1,Example0x2} 这使你能够轻松地为代码段添加多个标签 snippet.Tag Tag.Tip | Tag.Example 这可以改善数据封装因为你不必担心通过Tag property getter暴露内部集合。 等价比较 有两种类型的等价 引用相等这意味着两个引用引用了同一个对象。值平等这意味着两个不同的对象是等值的。 此外C提供了多种方法来测试等价。最常见的方法是使用 和运算符继承自Object的虚拟Equals方法静态Object.Equals方法IEquatable接口的Equals方法静态Object.ReferenceEquals方法 可能难以知道预期的是引用相等还是值相等。如果你重写Equals不要忘记IEquatable TGetHashCode如MSDN中所述。 注意无类型容器对重载的影响。考虑比较“myArrayList [0] myString”。数组列表元素是编译时类型“对象”因此使用引用等价。C编译器会警告你这个潜在错误但是有许多类似的情况编译器不会对意外的引用相等发出警告。 3. 类陷阱 封装你的数据 类负责正确地管理数据。出于性能原因它们通常缓存部分结果或者对其内部数据的一致性做出假设。数据公开访问会影响你缓存或做出假设的能力对性能安全性和并发性都有潜在影响。例如暴露可变成员如通用集合和数组允许用户在你不知情的情况下修改这些结构。 属性 属性使你能够精确控制用户如何与你的对象进行交互除了你通过访问修改器控制的之外。具体来说属性使你能够控制读取和写入时发生的情况。 属性使你能够建立稳定的API同时重写getter和setter中的数据访问逻辑或提供数据绑定源。 不要也不要让属性获取器抛出异常避免修改对象状态。这样就意味着需要一种方法而不是属性获取器。 有关属性的详细信息请参阅MSDN的属性设计主题http : //msdn.microsoft.com/en-us/library/ms229006(vvs.120).aspx 仔细的使用getters因为它有副作用。开发者认为成员访问是一个微不足道的操作所以他们经常忘记在代码审查期间考虑带来的副作用。 对象初始化 你可以在创建表达式时对新创建的对象设置属性。使用特定值来创建Class Cde 对象并用到Foo和Bar属性 new C {Fooblah, Barblam} 你还可以使用特定的属性名称创建匿名类型的实例 var myAwesomeObject new {Name”Foo”, Size10}; 初始化会在构造主体运行之前执行确保在进入构造器之前字段已经初始化了。因为构造函数还没有运行所以字段初始化器不能以任何方式引用“this”。 过度指定输入参数 为了帮助防止特定方法的过度使用请尝试采用方法所需的最小特定类型。例如考虑一个迭代List Bar的方法 public void Foo(ListBar bars)
{ foreach(var b in bars){ // do something with the bar...}
} 对于其他的IEnumerable Bar集合这段代码能够很好地运行但是通过为参数指定List Bar你就需要集合必须是一个List。选择参数的最小特定类型IEnumerable TICollection T等以确保方法的最大有用性 4. 泛型 泛型是一种十分有效的方式来定义与类型无关的结构体和确保类型安全的算法。 使用诸如List T之类的泛型集合而不是无类型的集合如ArrayList能够提高类型的安全性和性能。 当实现泛型类型时可以使用“default”关键字来获取那种无法硬编码到实现中的默认值。具体来说就是数字类型的默认值为0; 引用和可空值类型的默认值为null。 T t default(T); 5. 类型转化 有两种类型的conversions转化。显式转换必须由开发人员调用隐式转换由编译器基于上下文来应用。 Cast描述Tree tree Treeobj;如果obj是tree类型时请使用这个。如果obj不是Tree类型将产生一个InvalidCast异常。Tree tree obj as Tree;当你无法确定obj是否是Tree类型时请使用这个。如果obj不是Tree类型将会给Tree分配一个空值。在必要时请使用这种转换方式因为它需要对返回值进行条件处理。这些额外的代码可能产生更多的错误使得代码更难以读取和调试。 类型转化时经常会遇到以下两种情形 表达式的运行时类型比编译器能推断出的类型更加具体。转换指示编译器将表达式当做更具体的类型来处理。如果你的假设不正确编译器将抛出异常的代码。例如从对象到字符串的转换。转换指示编译器会生成关联表达式的值的代码如果没有生成则会抛出异常。例如从double到integer的转换。 两种类型转换都是很危险的。第一种类型的转换提出了一个问题“为什么开发人员知道而编译器不知道”如果在这种情况下尝试更改程序以便编译器可以成功地推导出正确的类型。如果你认为一个对象的运行时类型可能比编译时类型更具体那么你可以使用“is”或“as”运算符。 第二种类型转换引发了一个问题“为什么操作是在开始的地方执行的而不是在目标数据类型”如果你需要一个int类型的结果使用int比double更有意义。 有关其他想法请参阅http//blogs.msdn.com/b/ericlippert/archive/tags/castoperator/ 在显式转换是正确的操作情况下通过使用适当的运算符来提高可读性调试能力和可测试性。 6. 异常 异常不是条件 异常通常不应用于控制程序流; 它们代表的是在运行时你可能无法恢复的意外情况。如果你预期你应该处理的情况主动检查情况而不是等待异常发生。 要将格式不正确的字符串正常转换为数字请使用TryParse方法; 而不是抛出异常它返回一个布尔值指示解析是否成功。 使用异常处理范围 在catch内部写代码并且仔细处理成程序块。已执行过的代码已经不存在这些异常。例如 Frobber originalFrobber null;try {originalFrobber this.GetCurrentFrobber(); this.UseTemporaryFrobber(); this.frobSomeBlobs();
} finally { this.ResetFrobber(originalFrobber);
} 如果GetCurrentFrobber抛出异常那么当finally block被执行时originalFrobber仍然为null; 明智的处理异常 只捕获你准备处理的特定异常并且只针对特定代码段。除非你的意图只是简单的记录并重新抛出异常。某些例外可能使应用程序处于一种状态那么就需要避免处理所有异常或根类异常的实例。最好是在没有进一步损坏的情况下应用已经崩溃而不是试图恢复并造成损害。你的恢复尝试可能会在不经意间使事情更糟。 处理致命异常有一些细微差别特别是关于finally block执行时如何影响异常安全和调试器。有关详情请参阅http : //incrediblejourneysintotheknown.blogspot.com/2009/02/fatal-exceptions-and-why-vbnet-has.html 使用最高级异常处理来安全到处理程序的意外情况并公开信息以帮助调试问题。请谨慎使用catch块来解决本可以安全处理的特定情况为无法预料的异常预留最高级的处理。 如果你捕获到一个异常那么就需要采取一些措施来处理。不计其它后果地处理当前异常只会使问题难以识别和调试。 对于公开了工作API的代码来说将异常包含于自定义异常中是特别有用的。异常是方法的可见接口的一部分应该与参数和返回值一起被控制。可能导致更多异常的方法是不应该被使用在可维护解决方案中的。 抛出和重新抛出异常 当你希望在更深层次处理一个捕获到的异常时维护原始异常状态和堆栈对于调试有极大的帮助。需要仔细地平衡调试和安全注意事项。 简单的重新抛出异常也是一个好选择 throw; 或者在新的throw中使用异常作为InnerException throw new CustomException...ex; 不要显式地重新抛出捕获的异常如下所示 throw e; 这将复位异常状态到当前行并且阻止调试。 一些异常发生在代码的上下文之外。对于这些情况你可能需要添加事件的处理程序如ThreadException或UnhandledException而不是使用catch块。例如表单处理程序线程的上下文中引发的Windows窗体异常。 数据完整性 异常不得影响数据模型的完整性。你需要确保你的对象处于一致的状态 - 不会违反类实现所做的任何假设。否则通过“恢复”你只能使你的代码变得混乱之后还会导致进一步的损害。 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 subscribersEventHandler handler SomethingHappened; if (handler ! null)handler(this,new EventArgs());
} 更多关于时间和竞逐的信息请参阅http : //blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx 8. 属性 属性提供了一种方法用于将组件类和属性的元数据与其属性的信息一起输入。它们通常用于向代码用户提供信息如代码调试器测试框架和应用程序。你可以定义自己使用的属性也可以使用表中列出的预定义属性。 属性使用目的Debugger显示调试器调试器显示格式InternalsVisibleTo会员访问能将内部成员暴露给特定的其他类。使用它测试例程可以访问受保护的成员。默认值属性指定属性的默认值。 小心使用DebuggerStepThrough属性如果应用了这个属性会导致很难在方法中找到bug因为你不能单步执行或打断它们 9. 调试 调试是任何开发工作中重要的组成部分。除了提供对运行时环境的常规不透明方面的可见性之外调试器可以进入运行时环境同时调试器还会导致应用程序的在没有调试器的情况下获的不同的结果。 使异常堆栈可见 要查看当前框架的异常状态可以在Visual Studio Watch窗口中添加表达式“$ exception”。此变量包含当前异常状态类似于你在catch块中看到的情况除非你可以在调试器中看到异常状态否则就不必在代码中实际捕获异常。 注意访问器中的副作用 如果你所使用的属性有副作用请考虑是否应使用属性或调试器设置来防止调试器自动调用getter。例如你的类可能具有这些属性 private int remainingAccesses 10;private string meteredData;public string MeteredData
{ get{ if (remainingAccesses-- 0) return meteredData; return null;} } 第一次在调试器中查看此对象时remainingAccesses将显示为值10MeteredData显示为null。如果你将鼠标悬停在remainingAccesses上你会看到它的值现在是9。调试器显示的属性值已经改变了对象的状态。 10. 优化 早做计划经常衡量然后优化 在设计期间设置合理的性能目标。在开发期间专注于正确性而不是细微优化。经常根据目标衡量你的效果。如果你没有达到目标则应该花费时间来优化程序。 始终采用最合适的工具在具有可重复性和尽可能接近用户所经历的实际条件的情况下对性能进行经验性测量。 由于CLR优化有时效率低下的代码实际上比高效的代码运行速度更快。例如CLR优化覆盖了整个数组的循环以避免隐式的单元范围检查。开发人员通常在循环数组之前计算长度 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. 资源管理 垃圾回收器能够自动清理内存。即使如此所有一次性资源也必须妥善处理 - 特别是那些不由垃圾收集器管理的资源。 资源管理问题的常见来源内存碎片如果没有足够大的连续块的虚拟地址空间分配将失败。过程限制进程通常访问系统可用的内存和资源的严格子集。资源泄漏垃圾回收器只管理内存。其他资源需要由应用程序正确管理。资源困境依赖于垃圾收集器和终结器的资源在不再使用时不会变得立即可用。事实上它们可能永远不可用。 使用try / finally块来确保资源正确释放或让你的类实现IDisposable并利用更清洁和更安全的using语句。 using (StreamReader readernew StreamReader(file))
{ //your code here 避免代码中使用垃圾收集器 尽量不要通过调用GC.Collect干扰垃圾收集器而应该将重点放在正确释放或处置资源。当测量性能时如果你能够正确的评估影响在小心的让垃圾收集器运行。 避免编写终结器 不同于最流行的错误认知你的类不需要一个Finalizer仅仅是因为它实现IDisposable你可以实现IDisposable以使你的类能够在任何所有的复合实例上调用Dispose但是终结器只应在直接拥有非托管资源的类上实现。 Finalizer主要用于调用interop API来处理Win32句柄SafeHandle更容易处理。 你不能推测你的终结器 - 它总是在终结器线程上运行 - 可以安全地与其他对象交互。那些其他对象本身可能已经完成了。 12. 并发 并发和多线程编程是一件很复杂和困难的事情。在向应用程序添加并发之前请确保你真正了解自己正在做什么 - 有很多细微之处需要了解 多线程应用程序非常难以推理并且容易受到诸如通常不影响单线程应用程序的竞争条件和死锁等问题的影响。鉴于这些风险你应该最后才考虑多线程。如果你必须使用多个线程请尽量通过不在线程之间共享内存来最小化同步的需要。如果必须同步线程请使用最高级别的同步机制。 最高级别这些机制包括 Async-await/Task Parallel Library/LazyTLock/monitor/AutoResetEventInterlocked/SemaphoreVolatile fields and explicit barriers C/ .NET中并发的复杂性很难就在这里解释清楚。如果你想要或需要开发一个利用并发的应用程序请查看详细的文档如OReilly的“Concurrency in C# Cookbook”。 使用volatile 将字段标记为“易变”是高级功能即使专家也经常误解。C编译器将确保访问字段具有获取和释放语义; 这不同于确保对该字段的所有访问都处于锁定状态。如果你不知道什么是获取和释放语义以及它们如何影响CPU级优化则应避免使用volatile字段。相反应该使用较高级别的工具如任务并行库或CancellationToken类型。 利用线程安全的内置方法 标准库类型通常提供方便线程安全访问对象的方法。例如Dictionary.TryGetValue。使用这些方法通常使你的代码更清洁你不需要担心如TOCTTOU or TOCTOU场景等数据竞争的情况。 不要锁定“this”字符串或其他常见的公共对象 当实现在多线程上下文中使用的类时要非常小心使用锁。锁定此字符串或其他公共对象会阻止封装锁定状态并可能导致死锁。你需要防止其他代码锁定你的实现上正在使用的对象; 最安全的做法是使用一个私人的对象成员。 13. 避免常见错误 引用null 不适当的使用null是编码缺陷的常见来源可能会导致程序崩溃和其它意外行为。如果你尝试访问一个空引用以为它是一个对象的有效引用一样 - 例如通过访问一个属性或方法运行时将抛出一NullReferenceException异常。 静态和动态分析工具可以帮助你在发布代码之前识别潜在的NullReferenceException异常。在C中空引用通常由尚未引用对象的变量引起。对于空值类型和引用类型来说Null是一个有效值。例如Nullable Int空委托取消订阅事件会在“as”转换以及在许多其他情况下失败。 每个空引用异常是都一个错误。不应该去捕获NullReferenceException而应该尝试在使用它们之前测试对象是否为null。这也使得代码更容易被最小化try / catch块读取。 从数据库表中读取数据时请确保缺失值可以表示为DBNull对象而不是空引用。不要指望它们表现的像潜在的空引用。 将十进制值替换为二进制数 浮点数和双精度表示二进制有理数不是小数有理数在存储十进制值时必须使用二进制的近似值。从十进制的角度来看这些二进制近似具有不一致的舍入和精度 - 有时导致算术运算的意外结果。因为浮点运算通常在硬件中执行硬件条件可能会不可预测地加剧这些差异。 当小数精度非常重要时使用十进制就像财务计算等情况。 修改结构 一个常见的错误情况是忘记结构体是值类型的这就意味着它们被复制了并且通过值来进行传递。假设你有这样的代码 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; 现在当在M中调用Np时p具有错误的值。调用助手p传递p的副本而不是p的引用因此Helper中执行的变化将丢失。相反Helper会返回修改的p的副本。 意外的算术 C编译器保护你出现常量的算术溢出但不一定是计算值。 忽略保存返回值 与结构体不同类是引用类型方法可以修改引用的对象。然而不是所有的对象方法都实际修改了引用的对象一些会返回一个新对象。当开发人员调用后者时他们需要记住将返回值赋给变量以便使用修改后的对象。在代码审查期间这种类型的问题通常在会被发现。一些对象如字符串是不可变的所以方法从不修改这些对象。即使如此开发人员也会通常忘记。 例如考虑string.Replace string label “My name is Aloysius”;
label.Replace(“Aloysius”, “secret”); 代码打印“我的名称是Aloysius”因为Replace方法不修改字符串。 不要使迭代器/枚举器变得无效 不要在迭代时修改集合。 ListInt myItems new ListInt{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 如果你运行这个代码一旦循环到集合中的下一个项目时。你会收到一个异常抛出。 正确的解决方案是使用第二个列表来保存你要删除的项目然后在删除时迭代该列表 ListInt myItems new ListInt{20,25,9,14,50};
ListInt toRemove new ListInt();foreach(int item in myItems)
{ if (item 10){toRemove.Add(item); }
}foreach(int item in toRemove)
{ 或者如果你使用C3.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中的数据绑定会将属性名称指定为字符串。如果不小心更改该属性名称你将会无意中创建了一个编译器无法防护的问题。 以上就是所有C#开发人员应该知道的13件事情。 原文地址http://www.cnblogs.com/powertoolsteam/p/csharp.html .NET社区新闻深度好文微信中搜索dotNET跨平台或扫描二维码关注