在本书的第一部分,我们将简要介绍C#语言,并讨论它的一些特性。第1章介绍了什么是C#和.NET,以及为什么您会(也不会)在项目中使用它们。第2章深入探讨了.NET的各种迭代,并在编译过程中采用了C#方法,在编译过程的每一个主要步骤都停止下来 。
尽管这部分确实是本书的介绍,但它仍然为熟悉C#的人提供了宝贵的信息。前两章中介绍的一些知识是您在继续学习更高级主题之前需要了解的。
第1章 C#和.NET简介
本章包括
- 了解C#和.NET是什么
- 了解为什么要在项目中使用C#(以及为什么不使用)
- 切换到C#以及如何开始
你说是另一本关于C#的书吗?是的,另一个。很多书都是关于C#和.NET的,但这本书有一个根本的区别:我写这本书是为了帮助您在日常生活中开发干净、地道的C#代码。这本书不是一本参考书,而是一本实用指南。本书不涉及如何编写if语句、方法签名是什么或对象是什么之类的内容。我们不关心语法,而是关注概念和想法。了解语言的语法和能够编写干净、地道的代码是有区别的。读完这本书后,这正是你能够做到的。无论你的背景是什么,你知道什么编程语言,只要你了解面向对象编程,这本书将帮助你转向C#和.NET生态系统,如图1.1所示。
图1.1每一章的介绍都包含一个进度图,可以让您快速了解自己在书中的位置。
微软、谷歌和美国政府等组织有什么共同点?他们都使用C#,这是有充分理由的。但为什么?C#只是另一种编程语言。它与Java和C++有相似之处,支持面向对象和函数式编程,并得到了大型开源社区的广泛支持。太棒了现在,你为什么要在意?在本章中,我们将深入探讨这个问题,但让我透露一些剧透:C#擅长于允许您创建可扩展软件。要开始编写C#,您所需要的只是自己选择的.NET SDK(第2章将详细介绍),也许还有一个IDE。语言和运行时是开源的。
每当你在网上寻找C#时,很可能会遇到.NET框架。您可以将.NET Framework视为冬日的温暖毯子、温暖的炉火和一杯热巧克力,为您提供所需的一切:封装低级Windows API的库、公开常用的数据结构以及为复杂算法提供包装器。C#中的日常开发几乎肯定涉及到.NET Framework、.NET Core或.NET 5,因此我们将在适当的时候探讨这些框架。
图1.2显示了本书主题在通用.NET Web架构中的适用范围。它还显示了我们用来完全重写现有应用程序的架构,我们将从第5章开始(绿色/虚线箭头指示此路径)。
图1.2 Microsoft stack上的典型web服务架构示例。本书遵循绿色/虚线箭头所示的方法。本书涵盖了演示、业务逻辑和数据访问层。
对于那些有过C#经验的人:本书介于初学者和高级资源之间。通过本书中所教的技能,您可以弥补知识差距,并为高级技能做好准备。前两章对你来说可能有点基础,但我请你不要跳过这些。刷新你的知识总是很好的。
1.1 为什么使用C#?
如果您已经熟悉C#以外的编程语言并喜欢使用它,为什么要使用C#?也许你受雇于一家只使用C#的公司。或者你只是想看看这一切都是为了什么。
我保证不会重复告诉你,C#是一种“支持跨平台开发可扩展企业软件的强类型面向对象编程语言”。在本节中,我们只介绍了定义中的流行词,不再赘述。尽管听起来我是微软营销部门的员工,但在本节的其余部分,我们将重点关注以下C#的亮点和用例:
- C#(以及.NET生态系统)以经济的方式支持软件开发。经济的解决方案很重要,因为企业开发是C#的面包和黄油。
- C#可以提高代码的稳定性和可维护性,因为它支持自记录代码、安全库和易用性。
- C#对开发人员友好,易于使用。没有什么比发现你想要使用的编程语言没有很好的支持你喜欢的东西(例如稳定的包管理器、对单元测试的良好支持以及跨平台运行时)更糟糕的了。
当然,编写可扩展、可维护和开发人员友好的“干净代码”可以用大多数(如果不是所有)编程语言完成。不同之处在于开发人员的经验。有些语言非常擅长指导您编写干净的代码,而其他语言则不然。C#并不完美,但它确实试图在这方面帮助您。
1.1.1 原因1:C#很经济
C# 使用和开发是完全免费的。语言和平台是完全开源的,所有文档都是免费的,大多数工具都有免费的选项。例如,常见的C#安装程序包括安装C#8、.NET 5和Visual Studio社区。所有这些都是免费的,并在本书中使用。运行时不需要许可费,您可以在任何地方部署最终产品。
1.1.2 原因2:C#可维护
当我们在本书中谈到可维护性时,我们指的是修复bug、更改功能和解决其他问题而不会产生意外副作用的能力。这听起来像是任何编程语言的明显要求,但很难实现。C#具有提高大型代码库的可维护性(因此,安全可扩展性)的特性。例如,考虑泛型和语言集成查询(LINQ)。我们将在整本书中讨论这两件事,但它们是平台的一个例子,揭示了可以帮助您编写更好代码的功能。
对于一家公司来说,可维护性可能不是表面上的第一要务,但如果您开发的代码是可维护的(意味着干净的代码易于扩展并有测试支持),开发成本就会降低。在编写可维护代码时降低开发成本最初看起来可能违反直觉:可维护代码需要更长的时间来编写和构建,从而提高了开发的初始成本。然而,想象一下当用户发现一个bug或者他们想要一个额外的功能时,一段时间后会发生什么。如果我们编写了可维护的代码,我们可以快速轻松地找到错误(并修复它)。添加该特性更简单,因为代码库是可扩展的。如果我们可以轻松地扩展和修复代码库,开发成本就会降低。
打开/关闭原则
1988年,法国计算机科学家贝特朗·迈耶(Eiffel程序明语言的创造者)出版了一本名为《面向对象的软件构造》(Prentice Hall,1988)的书。迈耶的书的发布是面向对象编程和设计史上的一个关键时刻,因为在书中,他引入了开放/封闭原则(OCP)。OCP旨在提高软件设计的可维护性和灵活性。Meyer表示,OCP意味着“软件实体(类、模块、函数等)应开放进行扩展,但关闭进行修改。”
但OCP在实际意义上意味着什么?为了检验这一点,让我们将OCP应用于一个类:如果我们可以在不改变现有功能的情况下向类添加功能(因此,可能会破坏部分代码),那么我们认为一个类“开放”用于扩展,“封闭”用于修改。如果您遵守这一规则,那么在现有代码中引入回归(或新bug)的可能性要比在不考虑可维护性和可扩展性的情况下强行引入bug修复或新特性的可能性小得多。当您使用更复杂的代码时(并且是耦合的;在第8章中讨论过),您更可能由于错误理解更改的副作用而引入新的bug。这是我们不惜一切代价想要避免的。
1.1.3 原因3:C#对开发人员友好且易于使用
企业开发是C#开发的面包和黄油,也是C#和.NET的亮点所在。在企业环境中,理想的代码库是什么样子的?也许你想要一个代码库,它可以很容易地用一个可靠的软件包管理器进行导航,并有测试(单元、集成和烟雾)支持。让我们还提供优秀的文档和跨平台支持。
定义:自记录代码意味着代码写得足够清楚,我们不需要注释来解释逻辑。代码文档本身。例如,如果您有一个名为DownloadDocument的方法,其他人可以对它的作用有所了解。无需添加注释,说明方法内部的逻辑。
最重要的是,也许我们可以与云服务进行良好的集成,以实现持续集成和交付(CI/CD)。一个务实的观点告诉我们,你拥有这样一个代码库的可能性不是很高。当然,这个愿望清单对于大多数情况来说都是不切实际的。然而,如果你想做其中的一些事情(如果你喜欢冒险的话,也可以全部做),C#不会对你不利。它提供了现有的工作流程、功能和本机库,可以让您实现99%的工作效率。
来自Java等语言的开发人员应该在项目结构中看到一些相似之处。虽然存在一些差异,但它们并不大。我们将在全书中深入讨论C#项目结构。
.NET还支持几种流行的测试框架。Microsoft提供了Visual Studio单元测试框架,它包含(有时也被称为)MSTest。MSTest只是Visual Studio单元测试框架的命令行运行程序。其他常用的测试框架是xUnit和NUnit。您还可以找到Moq(Moq类似于Java的Mokito或Go的GoMock。我们将在10.3.3节中了解更多关于将Moq与单元测试一起使用的信息)、SpecFlow(类似于Cucumber的行为驱动开发)、NFluent(一个流畅的断言库)、FitNesse等等。
最后,您可以在许多平台上运行C#,尽管有一些限制(一些较旧的平台仅限于较旧版本的C#和.NETFramework)。使用.NET 5,您可以在Windows 10、Linux和macOS上运行相同的代码。这个功能最初是.NET Core,它是.NET Framework的衍生产品,后来与.NET Framework(以及其他框架)合并创建了.NET 5。您甚至可以通过Xamarin在iOS和Android上运行C#代码,也可以通过Mono在PlayStation、Xbox和Nintendo Switch平台上运行C#。
1.2 为什么不在C#中工作?
在任何情况下,C#都不是每个人的最佳选择。你必须为这份工作选择最好的工具。C#在各种情况下都能很好地工作,但在以下几种情况下,您可能不希望使用C#和.NET:
- 操作系统开发
- 实时操作系统驱动代码(嵌入式开发)
- 数值计算
让我们简要分析一下为什么C#可能不适合这些用例。
1.2.1 操作系统开发
操作系统(OS)开发是软件工程的一个极其重要的角落,但开发OS的人并不多。开发一个操作系统需要花费大量时间和精力,代码库通常会进入数百万行代码,经过多年甚至几十年的开发和维护。
C#不适合操作系统开发的主要原因归结为对手动内存管理(非托管代码)和C#编译过程的支持参差不齐。虽然C#允许在使用“不安全”模式时使用指针,但在内存管理的易用性方面,它不能与C这样的编程语言相媲美。
使用C#开发操作系统的另一个问题是它部分依赖于实时(JIT)编译器(第2章将对此进行详细介绍)。想象一下,必须通过虚拟机运行操作系统。性能将是一个问题,因为虚拟机必须一直进行追赶以运行JIT编译的代码,这与在计算机上运行.NET代码时发生的情况类似。这种批评意味着完全静态编译的语言更适合操作系统开发。
然而,用高级语言开发的操作系统的例子确实存在。例如,Pilot OS(由Xerox PARC于1977年创建)是用Java的前身Mesa编写的。
如果您想了解有关操作系统开发的更多信息,请访问osdev的wiki。org社区是一个极好的资源(wiki.osdev.org)。在那里,您可以找到入门指南、教程和阅读建议。学习C的资源包括Jens Gustedt的《Modern C》(Manning,2019)和Brian Kernighan和Dennis Ritchie的经典著作《The C Programming Language》(Prentice Hall,1988)。
1.2.2 C#中的实时操作系统嵌入式开发
与操作系统开发(第1.2.1节)类似,实时操作系统(RTOS)驱动的代码(在嵌入式系统中最常见)在通过虚拟机运行时会遇到很大的性能问题。RTOS实时线性扫描代码,并以可配置的间隔执行指令,该间隔范围从每秒一次操作到每微秒多次操作,这取决于开发人员的意愿和代码运行的微控制器或可编程逻辑控制器(PLC)的能力。由于增加了运行时的延迟和开销,虚拟机阻碍了真正的实时执行。
如果你想了解更多有关RTOS驱动的代码和嵌入式开发的信息,你可以看看几本备受推崇的书,比如David E.Simon的《An Embedded Software Primer》(Addison Wesley Professional,1999),或者Elecia White的《Making Embedded Systems: Design Patterns for Great Software》(O'Reilly Media,2011)。
1.2.3 数值计算和C#
数值计算(也称为数值分析)涉及算法的研究、开发和分析。从事数值计算的人(通常是计算机科学家或数学家)使用数值近似来解决几乎所有科学和工程分支中的问题。从编程语言的角度来看,它提出了独特的挑战和考虑。每种编程语言都可以计算数学语句和公式,但有些语言是专门为此目的而构建的。
考虑绘制图形。C#绝对可以处理绘图,但与MATLAB相比,C#提供了什么性能和易用性?(MATLAB既是一种计算环境,也是MathWorks创建的编程语言。)简单的答案是,它无法进行比较。C#中的图形编程可以让您在WPF(使用Direct3D)、OpenGL、DirectX或其他第三方图形库(通常针对视频游戏)中工作。有了MATLAB,你就有了一种与渲染复杂三维图形的环境相联系的语言。您可以直接调用plot(x,y),MATLAB为您绘制图形。
因此,C#可以进行数值计算,但不能像MATLAB那样提供与高级库和抽象处理图形绘制的语言相同的易用性。如果您有兴趣了解更多有关MATLAB或数值计算的信息,这些主题的一些可用资源包括Richard Hamming的《Numerical Methods for Scientists and Engineers》(Dover Publications,1987)、Amos Gilat的《MATLAB: An Introduction with Applications》(Wiley,2016)以及MATLAB的Cody教程程序(https://www.mathworks.com/matlabcentral/cody)。
1.3 切换到C#
由于语言之间的相似性,对Java虚拟机(JVM)语言(最著名的是Java、Scala和Kotlin)或C++的语法有很好理解的开发人员可能会比来自非C风格语言、非虚拟机风格语言或以web和云为中心的语言(如Dart、Ruby或Go)的开发人员更容易阅读本书。来自非C风格语言背景并不意味着C#不可能被理解。你可能会发现自己重读了一些段落两遍,但最后,你会很好地读到。
如果您来自一种解释语言(如Python),则.NET编译过程最初可能看起来很奇怪。.NET范围内的语言使用两步编译过程。首先,代码被静态编译为一种称为公共中间语言(Common Intermediate language,简称CIL、IL或MSIL;MS for Microsoft,对于我们中的Java开发人员来说,它有点类似于Java字节码)的低级语言,而当.NET运行时在主机上执行代码时,它又将即时(JIT)编译为本机代码。所有这些听起来可能很难突然消化,但在几章之后,你就会明白了。
如果您来自诸如JavaScript之类的脚本语言,静态类型可能会限制您并使您感到沮丧。但一旦你习惯了知道自己的类型,我想你会喜欢的。
如果你来自Go或Dart这样的语言,在那里有时很难找到本地库,.NET 5可能会以其丰富的库库让你大吃一惊。通过为您能想到的大多数内容提供函数,.NET库是您的主要功能源。许多用.NET编写的应用程序从不使用任何第三方库。
为了避免内务管理,让我们讨论一下工具。在本章中,我们将不深入探讨如何安装IDE或.NETSDK。如果您尚未安装.NET SDK或IDE,并且需要帮助,可以在附录C中找到一些快速安装指南。要继续阅读本书,您需要安装最新版本的.NET Framework和.NET 5。在本书中,我们将从使用.NET Framework的旧代码库开始。因此,当我们将代码迁移到.NET5时,我们将使用.NETFramework运行旧代码库。
如前所述,C#是开源的,由社区在Microsoft的帮助下维护。您不需要为运行时、SDK或IDE许可证付费。关于IDE,Visual Studio(我们将在本书的示例中使用的IDE)有一个免费的社区版,您可以使用它来开发个人项目和开源软件。如果你喜欢你当前的IDE,你很可能会找到一个C#插件。您还可以使用命令行来编译、运行和测试C#项目,尽管我鼓励您给专用的C#工具(Visual Studio)一个机会,因为它提供了编写惯用C#代码的最流畅的体验和最简单的途径。
您在其他地方学到的许多概念和技术都转移到了C#,但有些没有。C#在后端比在前端更成熟,因为它传统上主要用于这一目的。历史上对C#后端开发的关注并不意味着前端体验就不那么令人印象深刻。您可以用C#编写一个完整的stack应用程序,而无需接触JavaScript。虽然本书侧重于后端开发,但这里教给您的许多概念也能帮助您实现前端开发。
你有没有遇到过一个包含五个嵌套for循环、一堆硬编码数字(所谓的魔术数字)和比代码更多注释的怪物方法?假设你是一个刚加入团队的新开发人员。当您启动IDE、下拉源代码并看到此方法时,您会有什么感觉?绝望无法完全掩盖。现在想象一下,您将怪物方法中的所有单独操作都放在它们自己的小方法中(可能少于5到10行代码)。你的怪物方法是什么样子的?代码不像是一堆难以理解的条件和任务,除非你有特定的领域知识,否则没有清晰的理解路径,代码读起来就像是一个故事。如果你能很好地说出你的方法,你的主要方法应该读起来像一个食谱,即使是最糟糕的厨师也能遵循。
当我提到“干净的代码”时,我指的是罗伯特·C·马丁在他的视频中宣扬的编码实践(https://cleancoders.com/videos)以及《清洁代码》(Prentice Hall,2008)、《Clean Code》(Preantice Hall)(2017),以及与Micah Martin合著的《Agile Principles, Patterns, and Practices in C#》(Pearson,2006),以及通过他对“SOLID”原则(单一责任原则、开放/封闭原则、Liskov替代原则、界面隔离原则和依赖反转原则)的汇编。我在书中详细解释了干净代码原则,以及如何实际使用它们的实用信息。
说到底,为什么要编写干净的代码呢?干净的代码就像洗衣机一样处理错误和不正确的功能。如果我们将代码库放在干净的代码清洗机中,如图1.3所示,我们会看到,一旦您重构一些东西以使其更“干净”,就会出现错误,错误的功能会无处藏身。毕竟,“一切都是水落石出”。当然,重构生产代码也是有风险的;通常会产生意想不到的副作用。这使得管理层很难批准没有附加功能的大型重构。然而,使用正确的工具(本书中讨论了其中一些工具),您可以最大限度地减少负面副作用的机会,并提高代码库的质量。
图1.3 干净的代码就像一台洗衣机。它将你的脏衣服(你的代码),加入肥皂和水(清洁代码原则),并从衣服上分离污垢(从代码中分离bug)。它留给你的是衣服(代码),污垢(bug)比你开始时少。
这本书包含了关于干净代码主题的信息。如果边栏与干净的代码相关,我将它们表示为这样,并解释这两个概念以及如何将它们应用于现实世界。附录B包含一份清洁代码清单。您可以使用检查表来确定是否需要重构现有代码。清单提醒了一些更容易忘记(但仍然重要)的概念。
1.4 你将在本书中学到什么
本书将教您编写地道而干净的C#代码。它不从头开始教C#语言、.NET 5或编程。我们遵循一种实用的方法:一种业务场景,在该场景中,我们重构旧的API以使其更加干净和安全。一路上,你会学到很多东西。以下是一些亮点:
- 采用旧代码库并对其进行重构,以提高安全性、性能和清洁度
- 编写可通过任何代码审查的自记录代码
- 使用测试驱动开发与实现代码一起编写单元测试
- 通过实体框架核心安全连接到云数据库
- 将干净代码原则引入现有代码库
- 阅读通用中间语言并解释C#编译过程
那么,你需要知道什么才能充分利用这本书?期望您了解面向对象编程的基本原理(继承、封装、抽象和多态性),并熟悉另一种支持通过面向对象方法开发代码的编程语言(无论是C++、Go、Python还是Java)。
阅读本书后,您将编写干净、安全、可测试的C#代码,这些代码遵循良好的面向对象设计原则。此外,您将准备通过高级资源进一步加深您的C#知识。本书之后的一些建议读物包括乔恩·斯凯特的《C# in Depth》,第4版(曼宁,2019),杰弗里·里希特的《CLR via C#》,第四版(微软出版社,2012),比尔·瓦格纳的《Effective C#》第二版(微软社,2016),达斯汀·梅茨加的《.NET Core in Action》(曼宁出版社,2018),约翰·史密斯的《Entity Framework Core in Action》,第二版,Andrew Lock的《ASP.NET Core in Action》,第二版(Manning,2021)。
1.5 本书中你不会学到的东西
本书旨在填补初学者和高级C#资源之间的空白。有了这个目标,我对你对C#和编程的理解做出了什么样的假设。正如这里简要讨论的那样,我希望您有一些专业的编程经验,并且您熟悉C#的基础知识或不同的面向对象编程语言。
我这是什么意思?为了充分利用这本书,您应该了解面向对象的原则,并能够用您喜爱的编程语言开发基本应用程序或API。因此,本书没有教给您初学者编程书中经常出现的以下主题:
- C#语言本身。这不是一本遵循《从头开始的C#代码》的书。相反,我教你如何将现有的C#或面向对象编程知识提升到下一个层次。
- 围绕不特定于C#的条件语句和分支语句的语法(if、for、foreach、while、do while等)。
- 什么是多态性、封装和继承(尽管我们在本书中经常使用这些概念)。
- 什么是类以及我们如何通过类来建模真实世界的对象。
- 什么是变量,或者如何为变量赋值。
如果你是编程新手,我强烈建议在阅读这本书之前,先阅读一本书,比如詹妮弗·格林的《Head First C#》,第4版(O'Reilly,2020)或哈罗德·阿贝尔森(Harold Abelson)、杰拉尔德·杰伊·苏斯曼(Gerald Jay Sussman)和朱莉·苏斯曼(Julie Sussman,《Structure and Interpretation of Computer Programs》,第2版(麻省理工学院出版社,1996)。
本书也没有介绍这些更专门的C#使用方法:
- 微服务架构。本书没有深入探讨微服务是什么以及如何使用它们。微服务架构在很大程度上是一种趋势,在许多用例中都很有用,但与C#或您如何像专业人员那样编写代码无关。了解更多关于微服务的三个精彩资源是Chris Richardson的《Microservices Patterns 》(Manning,2018)、Prabath Siriwardena和Nuwan Dias的《Microservices Security in Action》(Manning,2019)以及Christian Horsdal Gammelgaard的《Microservices in .NET Core》(Manning)。
- 如何在Kubernetes和/或Docker等容器化环境中使用C#。虽然非常实用,并且在许多企业开发环境中使用,但知道如何使用Kubernetes或Docker并不能保证您可以在C#中“像专业人员一样编写代码”。要了解更多有关这些技术的信息,请参阅Marko Lukša的《Kubernetes in Action》第二版(Manning,2021)、Elton Stoneman的《learn Docker in a Month of Lunches》(Manning)(2020年)和Ashley Davis的《Bootstrap-ping Microservices with Docker,Kubernete,and Terraform》(2021)。
- 除了多线程和锁之外,与C#的并发性(在第6章中讨论)。我们经常在高度线程化和性能关键的场景中发现这个主题。大多数开发人员不怎么使用这样的代码。如果你确实发现自己处于这个位置,那么了解更多关于C#并发编程的绝佳资源是乔·达菲的《Concurrent Programming on Windows 》(Addison Wesley,2008)。
- CLR或.NETFramework本身的深层内部细节。尽管CLR和.NET5很有趣,但了解它们的每一个细节对大多数开发人员来说都没有什么实际用处。本书涵盖CLR和。NET框架,但当事情变得不切实际或笨拙时就停止了。CLR和.NET框架的“圣经”是Jeffrey Richter《CLR via C#》第4版(微软出版社,2012年)。
你有两种方法读这本书。推荐的方法是从头到尾按顺序阅读整本书。如果您只对重构和最佳实践感兴趣,可以阅读第3部分至第6部分。
总结
- 本书不涉及“编程101”,它假定了面向对象编程的知识。这使我们能够专注于实际概念。
- C#和.NET 5在可扩展的企业开发方面表现出色,注重稳定性和可维护性。这使得C#和.NET成为公司和个人开发人员的理想平台。
- C#和.NET5在操作系统开发、RTOS嵌入式开发或数值计算(或分析)方面并不出色。对于这些任务,C和MATLAB更适合。