核心 C#

本文介绍了C#编程的基础知识,包括变量的声明、可空类型的概念以及如何使用if、for、while和foreach等控制流程语句。重点讲述了C#的可空引用类型,旨在减少NullReferenceException异常,以及如何在不同循环结构中控制程序流程。此外,还讨论了字符串的使用,如字符串插值和范围操作,以及预处理器指令,如#define、#undef和#pragma的作用。
摘要由CSDN通过智能技术生成

 

2.1  C#基础

        理解了C#的用途后,就该学习如何使用它了。本章将介绍C#编程的基础知识,这也是后续章节的基础。学习完本章后,读者就有足够的C#知识来编写简单的程序了,但还不能使用继承或其他面向对象的特性。这些内容将在后面的几章中讨论。
       首先对C#语法做一些一般性的介绍:
  • 语句都以分号(;)结尾,并且语句可以写在多个代码行上,不需要使用续行字符。
  • 用花括号({})把语句组合为块。
  • 单行注释以两个斜杠字符开头(//)。
  • 多行注释以一个斜杠和一个星号(/*)开头,以一个星号和一个斜杠(*/)结尾。
  • C#区分大小写,即 myVar 与MyVar 是两个不同的变量。

2.1.1  顶级语句

         C#9中新增了一种名为顶级语句的语言特性。在创建简单的应用程序时,可以不定义名称空间、不声明类,也不定义MainO方法。只包含一行代码的“Hello World!”应用程序如下所示:
System. Console.WriteLine("Hello World!");
我们来增强这个只包含一行代码的应用程序,以打开定义Console类的名称空间。通过使用using 指令导入System名称空间后,可以直接使用Console类,而不必为其添加名称空间前缀:
using System;
Console.WriteLine("Hello World!");
因为WriteLine( )是Console类的静态方法,所以甚至可以使用using static 指令打开Console类:
using static System.Console;
WriteLine("Hello World!");
使用顶级语句时,编译器会在后台创建一个包含 Main( )方法的类,并把顶级语句添加到Main( )
方法中:
using System;
class Program
{
  static void Main()
     {
          Console.WriteLine("Hello, World!");
     }
}
注意:
本书的许多示例使用了顶级语句,因为这种功能对于小型示例应用程序极为有用。对于很小的微服务(现在只需要几行代码就可以编写出来),以及在类似脚本的环境中使用C#的时候,这种功能也有实际用途。

2.1.2 变量

         C#提供了 声明初始化变量的不同方式。变量有一个 类型和一个随着时间可能发生改变的 。在下面的代码片段中,变量名称左侧的类型声明定义了该变量的类型,所以变量s1 的类 型是string。变量s1被初始化为一个新的字符串对象,将字符串字面量“Hello,World!” 传递给了构造函数。
        因为string类型非常常用,所以除了创建新的字符串对象,也可以把“Hello,World!”字符串直接赋值给变量(变量s2采用了这种方法)。
C#3引入了支持类型推断的 var关键字,它也可以用来声明变量。在这里,右侧的值是有类型的,左侧将从该值 推断出类型。
编译器从字符串字面量“Helo,World!”创建一个字符串对象,s3也是类型安全的、强类型的字符串,就像s1和s2那样。
c#9提供了一种新的语法来声明和初始化变量: 目标类型的new 表达式。当左侧知道了变量的类型后,就不必编写 new string(“Hello,Wor!”)这样的表达式,而是可以使用表达式new(“Hello,World!”)。不必在右侧指定类 :
using System;
string s1 = new string("Hello, World!");
string s2 = "Hello, World!";
var s3 = "Hello, World!";
string s4 = new("Hello, World!);
Console.WriteLine(sl);
Console.WriteLine(s2);
Console.WriteLine(s3);
Console.WriteLine(s4);
//...
注意:
      无论是在左侧使用var关键字声明类型,还是使用目标类型的new表达式,基本都只是个人喜好问题。在后台,会生成相同的代码。从C#3开始提供var关键字,之前需要在左侧通过定义类型来声明类型,并且实例化对象时还需要在右侧指定类型,而var关键字减少了需要编写的代码量。
使用var关键字时,只需要在右侧指定类型。但是,对类型的成员不能使用var关键字。
     在C#9之前,对于类成员,需要写两次类型,现在则可以使用目标类型的new表达式。 目标类型的new表达式可用于局部变量,如前面代码片段中的变量s4所示。这并不会让var关键字失去作用,它依然有自己的优势,例如在接收方法的返回值时使用。

2.1.3  命令行实参

           当启动程序,向应用程序传递值的时候,对于顶级语句,将自动声明变量args。在下面的代码片段中,通过使用foreach语句访问变量args,遍历全部命令行实参,并将它们的值显示到控制:
using System;
foreach (var arg in args)
{
   Console.WriteLine(arg);
}
使用.NET CLI运行应用程序时,可以在dotnet run后面加上--,然后将实参传递给程序。添加--是为了避免将.NET CLI的实参与应用程序的实参混淆:
>dotnet run -- one two three
运行这行代码,将在控制台看到字符串one two three。
创建自定义的Main()方法时,需要声明该方法接受一个字符串数组。你可以为该变量选择一个名称,但通常会将该变量命名为args。这也是为什么在为顶级语句自动生成变量时,使用了名称args:
using System;
class Program
{
  static void Main (string[] args)
  {
    foreach(var arg in args)
    {
      Console.WriteLine(arg);
    }
  }
}

2.1.4 变量的作用域

变量的作用域是可以访问该变量的代码区域。一般情况下,确定作用域遵循以下规则:
  • 只要类在作用域内,则其字段(也称为成员变量)也在作用域内。
  • 在到达声明局部变量的块语句或者方法的右花括号之前,局部变量都在作用域内。
  • 在for、while或类似语句中声明的局部变量的作用域是该循环体内部。
大型程序常常在不同部分为不同的变量使用相同的变量名。只要变量的作用域是程序的不同部分,不会导致多义性,就不会有问题。但要注意,同名的局部变量不能在同一作用域内声明两次。例如:不能使用下面的代码:
int x=20;
//some more code
int x=30;
考虑下面的代码示例:
using System;
for(int i=0;i<10;i++)
{
Console.WriteLine(i);
}//i goes out of scope here
//we can declare a variable named i again,because there's no other variable with that name in scope
for(int i=9;i>=0;i--)
{
  Console.WriteLine(i);
}//i goes out of scope here;
这段代码很简单,使用两个for循环先顺序打印0~9的数字,再逆序打印0~9的数字。重要的是在同一个方法中,代码中的变量i声明了两次。可以这么做的原因是,i在两个相互独立的循环内部声明,所以每个变量i对于各自的循环来说是局部变量。
下面是另一个例子:
int j=20;
for(int i=0;i<10;i++)
{
  int j=30; //Can't do this -j is still in scope
  Console.WriteLine(j+1);
}
如果视图编译它,就会产生如下错误:
error CS0136:A local or parameter named 'j' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter
其原因是:变量j是在for循环开始之前定义的,在执行for循环时仍处于其作用域内,直到Main()方法(由编译器创建)结束执行后,变量j才超出作用域。因为编译器无法区分这两个变量,所以不允许声明第二个变量。
即使在for循环结束后再声明变量j也没有帮助,因为无论在什么地方声明变量,编译器都会将所有变量声明移动到作用域内的顶部。

2.1.5 常量

对于从不会改变的值,可以定义一个常量。要定义常量值,需要使用const关键字。
使用const关键字声明变量后,在该变量出现的每个地方,编译器将使用常量值替换它。
通过在类型的前面使用const关键字来指定常量:
const int a=100; //This value cannot be changed.
该局部字段每次出现时,编译器就把它替换为它的值。在版本化时,这种行为很重要。如果在库中声明了一个常量,然后在应用程序中使用该常量,就需要重新编译应用程序来使用新值。否则,库中的值和应用程序使用的值可能不同。正因此,最好只对从不会改变的值(即使在将来的版本中也不会改变)使用const。
常量具有如下特点:
  • 常量必须在声明时初始化。指定了其值后,就不能再改写了。
  • 常量的值必须能在编译时计算出来。因此,不能用变量的值来初始化常量。如果需要这么做,就必须使用只读字段。
  • 常量总是隐式静态的。但注意,不必(实际上,是不允许)在常量声明中包含修饰符static。
在程序中使用常量至少有下面的好处:
  • 由于使用易读的名称(名称的值易于理解)替代了魔数和字符串,常量使程序变得更易于阅读。
  • 常量更容易避免程序出现错误。如果在声明常量的位置以外的某个地方将另一个值赋值给常量,编译器就会标记错误。
注意:如果多个实例可以具有不同的值,但这些值在初始化后不会再发生变化,那么可以使用readonly字段。

2.1.6 在顶级语句中使用方法和类型

在包含顶级语句的文件中,也可以添加方法和类型。在下面的代码片段中,定义了一个名为Method( )的方法,并在该方法的声明和实现后调用了它:
//...
void Method( )
{
  Console.WriteLine("this is a method");
}
Method( );
//...
可以在使用之前或之后声明该方法。在相同的文件中可以添加类型,但需要在顶级语句的后面指定它们。在下面的代码片段中,指定类Book包含一个Title属性和ToString( )方法。在类型声明前,创建了一个新的实例,并将其赋值给变量b1,还设置了Title属性的值,还设置了Title属性的值,然后将该实例写入控制台。当把对象作为实参传递给WriteLine( )方法时,将调用Book类的ToString()方法:
Book b1=new();
b1.Title="Professional C#";
Console.WriteLine(b1);
class Book
{
public string Title {get;set;}
public override string ToString()=>Title;
}
注意:所有顶级语句需要包含在一个文件中,否则编译器将不知道从哪个文件开始。如果使用了顶级语句,则应该让它们易于找到,例如,把它们添加到Program.cs文件中。你不会希望在多个文件中查找顶级语句。

2.2  可空类型

在C#的第一个版本中,值类型不能有null值,但总是可以将null赋值给引用类型。这种情况在C# 2中第一次发生改变,因为C# 2引入了可空值类型。C#8对引用类型做了修改,因为.NET中发生的大部分异常是NullReferenceException。当调用的引用成员的值为null时,就会发生这种这种类型的异常,为了减少这种问题,使它们成为编译错误,C#8引入了可空类型。

2.2.1 可空值类型

对于值类型,如int,不能向其赋值null。当映射到数据库或其它数据源(如XML或JSON)的时候,这可能导致一些难以处理的情况。使用引用类型则会造成额外的开销:对象存储在堆上,当不再使用该对象时,垃圾回收过程需要清理它。可以采用的方法是,在类型定义中使用”?“,这将允许赋值null:
int? x1=null;
编译器将把这行语句改为是哟个Nullable<T>类型:
Nullable<int> x1=null;
Nullable<T>不会增加引用类型的开销。它仍然是一个struct(值类型),只不过添加了一个布尔标志,用来指定值是否为null。
下面的代码片段演示了使用可空类型和不可为空值的赋值。变量n1是可空的int,它被赋值为null。可空值类型定义了HasValue属性,可以用它来检查变量是否被赋值。
使用Value属性可以访问该变量的值。这种方法可用来将值赋值给不可为空的值类型。总是可以把不可为空的值赋值给可空的值类型,这种赋值总会成功:
int? n1=null;
if(n1.HasValue)
{
int n2=n1.Value;
}
int n3=42;
int? n4=n3;

2.2.2  可空引用类型

可空引用类型的目标是减少NullReferenceException类型的异常,这是.NET应用程序中最常发生的异常类型。一直以来都有一个指导原则:应用程序不应该抛出这种异常,而总是应该检查null,但没有编译器的帮助,很容易就会漏掉值为null的情况。
为了能够获得编译器的帮助,需要启用可空引用类型。因为这种特性可能破坏现有代码,所以需要显式启用。为此,需要在项目文件中指定Nullable元素,并将其值设置为enable:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>
现在,不能把null赋值给引用类型。如果启用了Nullable,然后编写下面的代码:
string s1=null; //compiler warning
编译器将给出如下错误:CS8600:Converting a null literal or a possible null value to non-nullable type.。
要将null 赋值给字符串,需要在声明类型时加上“?”,就像可空值类型那样:
string? s1=null;
当使用可空的s1变量时,需要在调用方法或者把该变量赋值给不可为空的字符串之前,检查他的值不是null。否则,编译器将生成警告:
string s2=s1.ToUpper(); //compiler warning
通过使用null条件运算符“?.”,可以在调用方法前检查null。使用该运算符时,只有对象不为null,才会调用方法。其结果不能被写入不可为空的字符串,因为如果s1是null,则右侧表达式的结果为null:
string?  s2=s1?.ToUpper( );
通过使用空合并运算符“??”,可以在对象为空时指定一个不同的返回值。在下面的代码片段中,当??左侧的表达式返回null的时候,整个表达式将返回一个空字符串。右侧表达式的完整结果将写入变量s3,它不会为null。如果s1不为null,它就是s1字符串的大写版本,而如果s1是null,它就是一个空字符串:
string s3=s1?.ToUpper() ?? string.Empty;
除了使用这些运算符,还可以使用if语句确认变量不为null。在下面的代码片段的if语句中,使用了C#模式is not来验证s1不为null。只有当s1不为null的时候,才会调用if语句的块。在这里,不需要使用可空条件运算符来调用ToUpper()方法:
if(s1 is not null)
{
  string s4=s1.ToUpper();
}
当然,也可以使用不等于运算符!= :
if (s1 != null)
{
 string s5=s1.ToUpper();
}
对于类型成员,使用可空引用类型也很重要,如下面的代码片段中的Book类的Title和Publisher成员所示。代码中使用不可为空的string类型声明了Title,所以在创建Book类的新对象时,必须初始化Title。它是由Book类的构造函数初始化的。Publisher属性允许为null,所以不需要初始化:
class Book
{
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GiselleLu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值