02020201 .NET Core重难点知识01-C#顶级语句、全局using指令、文件范围的命名空间、可为空引用类型

02020201 .NET Core重难点知识01-C#顶级语句、全局using指令、文件范围的命名空间、可为空引用类型

1. C#顶级语句

  • 在C# 9.0之前,即使只编写一行输出“Hellow Qinway”,也需要创建一个C#类,并且要为这个C#添加Main方法,才能在Main方法中编写代码。
  • 从C# 9.0(VS 2022)开始,C#增加了顶级语句,它使得可以直接在C#文件中别写入口代码,不再需要声明类和方法。
// 最简单的C#代码
Console.WriteLine("Hello, Qinway.")

说明:
1. 通过反编译上述代码,可以看到编译器自动生成了一个Program类,并且把我们编写的代码放到了Program类中。
2. 顶级语句的作用是让编译器帮我们简化工作。
3. 同一个项目中只能有一个文件具有顶级语句。
4. 顶级语句并不是用来替代原来的Main方法的,我们仍然可以使用传统的Main方法编写入口代码。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 顶级语句的高级用法
int i = 1, j = 2;
int w = Add(i, j);
await File.WriteAllTextAsync("e:/1.txt", "Hellow Qinway"+w);
int Add(int i1, int i2)
{
    return i1 + i2;
}

说明:在顶级语句中,可以直接使用await语法调用异步方法,而且顶级语句中可以声明方法。

2. 全局using指令

  • 在C# 10.0 之前,编写项目代码的时候,在每个C#文件头部需要编写重复的using语句来引入不同的程序集下不同的命名空间,非常繁琐。
  • 从C# 10.0开始,新增了全局using指令。
    • 可以将global修饰符添加到任何using关键字之前,这样通过using语句引入的命名空间就可以应用到这个项目的所有源代码中。
    • 此时,同一个项目中的C#代码就不需要再去重复引入这个命名空间了。
    • 在实践中,通常创建一个专门用来编写全局using代码的C#文件,然后把所有在项目中经常用到的命名空间声明在这个C#文件中。
2.1 新建全局using指令文件
// Usings.cs文件
global using Microsoft.Data.Sqlite;
global using System.Text.Json;

说明:
1. 使用全局using指令,项目中其它的C#文件就不在需要去单独声明这些命名空间的using语句。
2. 只要在*.csproj文件中加入了<ImplicitUsing>enable</ImplicitUsing>,编译器会根据项目类型自动为项目隐式地增加对System,System.Linq、Microsoft.AspNetCore.Http等常用命名空间的引入。
2.2 using声明
  • C#中使用using关键字来简化非托管资源的释放。当变量离开using作用的范围后,会自动调用对象的Dispose方法,从而完成非托管资源的释放。
// 如果代码中有很多非托管资源需要被释放,会村镇多个嵌套的using语句,代码结构比较复杂。
using (var conn = new SqlConnection(connStr))
{
    conn.Open();
    using(var cmd = conn.CreateCommand())
    {
        cmd.CommandText = "select * from T_Articles";
        using (SqlDataReader reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {
                ...
            }
        }
    }
}
2.3 简化的using声明
  • 从C# 8.0开始,可以使用简化的using声明来避免代码的嵌套。在变量声明的时候,如果类型实现了IDisposable或IAsyncDisposable接口,那么在变量声明前加上using关键字,这样当代码执行离开被using修饰的变量作用域的时候,变量指向的对象Dispose方法就会被调用。
// 简化的using声明:使得using声明在保证资源回收的前提下,保持了代码的简洁。
using var conn = new SqlConnection(connStr);
conn.Open();
using var cmd = conn.CreateCommand();
cmd.CommandText = "select * from T_Articles";
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
    ...
}
  • 注意事项
    • 之前传统using语法可以由开发人员定义资源的回收机制。
    • 简化的using声明语法使得变量在离开作用域的时候,比如方法执行结束时,才进行资源回收。我们在使用这种语法的时候要避免一些可能的陷阱。
2.4 简化using声明的陷阱
// 有问题的代码
using var outStream = File.OpenWrite("e:../1.txt");
using var writer = new StreamWriter(outStream);
writer.WriterLine("Qinway");
string s = File.ReadAllText("e:/1.txt"); // @1
Console.WriteLine(s);

异常:System.IO.IOException:“The process cannot access the file 'e:\1.txt' because it is being used by another process.”

说明:
1. outStream和writer两个变量在方法执行结束后才被资源释放,程序在执行到@1处时,文件仍然被占用,因此在@1处的代码抛出了异常。
2. 如果希望上述的代码能够正常执行,要么使用传统的using语法进行资源回收,要么手动添加花括号把要释放的资源放到单独的作用域中。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 使用{}来实现资源的释放
{
    using var outStream = File.OpenWrite("e:../1.txt");
    using var writer = new StreamWriter(outStream);
    writer.WriterLine("Qinway");
}
string s = File.ReadAllText("e:/1.txt"); // @1
Console.WriteLine(s);

说明:通过{}构建一个独立的代码块,这个代码块就是一个独立的作用域,因此程序在离开这个作用域之后outStream和writer两个变量指向的对象就会被释放。

3. 文件范围的命名空间声明

// 在C# 10.0之前的命名空间声明
namespace Demo1
{
	class Teacher
    {
        public int Id {get; set;}
        public string Name {get; set;}
    }
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 从C# 10.0开始允许编写独立的namespace代码声明命名空间,使得文件中的所有类型都是这个命名空间下的成员,减少代码嵌套的层次。
namespace TMS.Admin;
class Teacher
{
	public int Id {get; set;}
	public string Name {get; set;}
}

4. 可为空的引用类型

  • 在C# 8.0之前,数据类型分为值类型和引用类型。
    • 值类型的变量不可以为空。
    • 引用类型的变量可以为空。
      • 如果在使用引用类型的时候,如果不检查引用类型变量是否为空,程序可能抛出NullReferenceException异常。
  • 从C# 8.0开始,提供了可为空的引用类型语法。
    • 可以在引用类型后添加?修饰符声明这个类型是可为空的。
    • 对于没有添加?修饰符的引用类型的变量
      • 当编译器发现存在为这个变量赋值null的可能性的时候,编译器会给出警告。
      • 在VS 2022中,这个特性是默认启动的。可以通过删除*.csproj文件中disable关闭这个特性。
4.1 没有应用可为空的引用类型的类
public class Student
{
    public string Name {get; set}
    public string PhoneNumber {get; set}
    public Student(string name)
    {
        this.Name = name;
    }
}

说明:
1. 上述代码在编译时,编译器会给出“在退出构造方法时,不可为null属性的PhoneNumber包含非null”这样的警告信息。
2. 如果要消除这个警告,可以在构造函数参数列表和方法体中增加对PhoneNumber属性的赋值。
3. 如果确实PhoneNumber可以为空,可以把PhoneNumber属性声明为可为空的引用类型。
4.2 使用可为空的引用类型的类
// @1 声明PhoneNumber为可为空类型
public class Student
{
    public string Name {get; set}
    public string? PhoneNumber {get; set} // 属性声明为string?类型,即允许为空的string类型。
    public Student(string name)
    {
        this.Name = name;
    }
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// @2 使用可为空类型会提示警告
Student s1 = GetData();
Console.WriteLine(s1.Name.ToLower());
Console.WriteLine(s1.PhoneNumber.ToLower()); // 在@1中PhoneNumber可为空,这里会出现警告。

Student GetData()
{
	Student s1 = new Student("Qinway");
    s1.PhoneNumber = "999";
    return s1;
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// @3 对可为空类型的成员进行检查
Student s1 = GetData();
Console.WriteLine(s1.Name.ToLower());
if (s1.PhoneNumber != null)
{
    Console.WriteLine(s1.PhoneNumber.ToLower());
}
else
{
    Console.WriteLine("电话号码为空");
}

Student GetData()
{
	Student s1 = new Student("Qinway");
    s1.PhoneNumber = "999";
    return s1;
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// @4 如果确认被访问的变量、成员不会出现为空的情况,可以在访问可为空的变量、成员时加上!来抑制编译器的警告。
Student s1 = GetData();
Console.WriteLine(s1.Name.ToLower());
Console.WriteLine(s1.PhoneNumber!.ToLower()); // 使用!来抑制已经确认不可为空的变量、成员的警告。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
综上:
1. 对于可为空的引用类型的属性,编译器会在属性上提那家NullableAttribute,因此可以在运行时通过反射判断一个引用类型属性的可空性。
2. 很多.NET下的框架都充分利用了可为空的引用类型,从而引用类型的属性、参数等进行更加智能化的处理。

结尾

书籍:ASP.NET Core技术内幕与项目实战

视频:https://www.bilibili.com/video/BV1pK41137He

著:杨中科

ISBN:978-7-115-58657-5

版次:第1版

发行:人民邮电出版社

※敬请购买正版书籍,侵删请联系85863947@qq.com※

※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※

posted @ 2025-08-24 09:28  qinway  阅读(65)  评论(0)    收藏  举报