• 第一节:再探C#语法(顶级语句、全局using、namespace、using管理、可空类型、record新类型)


    一. 顶级语句

    1. 直接在C#文件中直接编写入口方法的代码,不用类,不用Main.同时经典写法仍然支持,反编译一下了解真相.  
    2. 同一个项目中只能有一个文件具有顶级语句
    3. 顶级语句中可以直接使用await语法,也可以声明函数

    代码分享:

    Console.WriteLine("测试写入文件哦");
    await File.WriteAllTextAsync("1.txt", "hello");
    Console.WriteLine("写入完成");
    Console.ReadKey();

    反编译代码:

    二. global using

    1. 说明

       将 global 修饰符添加到 using 前,这个命名空间就应用到整个项目,不再需要重复每个文件使用using了。

    2. 常用套路

       通常创建一个专门用来编写全局using代码的C#文件。

       比如新建一个GlobalUsing.cs类,里面使用global using 导入需要使用的命名空间,那么在该项目中的其它文件使用这些命名空间下的文件方法的时候,不再需要using了

    3. 补充

       如果csproj中启用了<ImplicitUsings>enable</ImplicitUsings>,编译器会自动隐式增加对于System、System.Linq等常用命名空间的引入,不同各类型项目引入的命名空间也不一样。

    三. 文件范围的声明空间

       比如新建1个类,它都是在 namespace xxx { 类xxx},现在可以省略最外层的{},详见 utils/TestHelp2

    旧写法:

    namespace _02_基础总结.utils
    {
        internal class TestHelp2
        {
        }
    }

    新写法:

    namespace _02_基础总结.utils;
    internal class TestHelp2
    {
    }

    四. using管理

    1. 旧写法

      实现了IDisposible接口的对象可以用using进行管理, 形如 using(var xxx= new xxx){},  其中 {} 执行完毕,using中的内容dispose释放。

     string connStr = "Data Source=.;Initial Catalog=demo1;Integrated Security=True";
        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())
                    {
                    }
                }
            }
        }

    自己实现一个案例测试: 

      using (MyFile myFile = new MyFile())
        {
            Console.WriteLine("执行完毕");
        }

    运行结果:

     

    2. 新写法(C#8.0)

      在实现了Idisposable/IAsyncDisposable接口的类型的变量声明前加上using,当代码执行离开变量的作用域时(比如这些代码在一个函数里,那么就是离开这个函数),对象就会被释放。

      形如:using SqlConnection conn = new SqlConnection("xxx") ; 当离开conn的作用域的时候,conn将被释放 如下案例

    详细测试:using后面变量离开作用域释放问题

    前置代码:MyFile类

    public class MyFile : IDisposable
    {
        public void Dispose()
        {
            Console.WriteLine("MyFile被释放了");
        }
    }

    (1). 案例1--using作用域为所在的函数

        myFile的作用域为TestDispose所在的函数,当函数结束,myFile被释放,所以运行结果如下:

    // 测试1--using作用域为所在的函数
    {
        Console.WriteLine("start");
        TestDispose();
        Console.WriteLine("end");
    
    
        static void TestDispose()
        {
            using MyFile myfile = new MyFile();
            Console.WriteLine("TestDispose执行完毕");
        }
    
    }

     

     (2). 案例2--using作用域是为每次for循环结束 

        myFile的作用域为所在的for循环每次结束的时候释放,所以运行结果如下

    // 测试2--using作用域为每次for循环结束
    {
        Console.WriteLine("start");
        TestDispose();
        Console.WriteLine("end");
    
        static void TestDispose()
        {
            for (int i = 0; i < 2; i++)
            {
                using MyFile myfile = new();
                Console.WriteLine($"i={i}");
            }
            Console.WriteLine("TestDispose执行完毕");
        }
    }

     

    3. using声明陷阱

      如下例子,使用新的using写法,导致上面写文件的using还没释放,就又要去读文件,所以会报错

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

    //using陷阱
    /*
     System.IO.IOException: The process cannot access the file 'e:\1.txt' because it is being used by another process.
     */
    {
        //写文件
        using var outStream = File.OpenWrite("e:/1.txt");
        using var writer = new StreamWriter(outStream);
        writer.WriteLine("hello");
        //读文件
        string s = File.ReadAllText("e:/1.txt");
        Console.WriteLine(s);
    
    }

      解决方案:把上面写的using释放到即可

      方案1:using包裹的模式

      方案2:给using新写法加1层作用域

    //方案1-using包裹的模式
    {
        using (var outStream = File.OpenWrite("e:/1.txt"))
        using (var writer = new StreamWriter(outStream))
        {
            writer.WriteLine("hello");
        }
        string s = File.ReadAllText("e:/1.txt");
        Console.WriteLine(s);
    }
    //方案2-给using新写法加1层作用域
    {
        {
            using var outStream = File.OpenWrite("e:/1.txt");
            using var writer = new StreamWriter(outStream);
            writer.WriteLine("hello");
        }
        string s = File.ReadAllText("e:/1.txt");
        Console.WriteLine(s);
    
    }

    五. 可空引用类型

    1. 背景

      C#数据类型分为值类型和引用类型两种,值类型的变量不可以为空,而引用类型变量可以为空。

      问题:如果不注意检查引用类型变量是否可空,就有可能造成程序中出现NullReferenceException异常。

    2. 实操

      (1). csproj中<Nullable>enable</Nullable>启用可空引用类型检查

      (2). 在引用类型后添加“?”修饰符来声明这个类型是可空的。对于没有添加“?”修饰符的引用类型的变量,如果编译器发现存在为这个变量赋值null的可能性的时候,编译器会给出警告信息

      如:Nullable/student类中的name 和 array 是引用类型,需要加个?, 否则报警告。

     internal class Student
        {
            public int Id { get; set; }
            public string? Name { get; set; }
    
            public DateTime CreatedDate { get; set; }
    
            public string[]? array { get; set; }
    
        }

      (3). 如果程序员确认被访问的变量、成员确实不会出现为空的情况,也可以在访问可空的变量、成员的时候加上!来抑制编译器的警告。

       Console.WriteLine(s1.PhoneNumber!.ToLower());

      (4). 个人倾向:不喜欢开启Nullable

    六. record新类型

    1. 背景

       C#中的==运算符 和 equal方法 默认是判断两个变量指向的是否是同一个对象,即使两个对象内容完全一样,也不相等。可以通过重写Equals方法、重写==运算符等来解决这个问题,不过需要开发人员编写非常多的额外代码。

    代码分享:

     public class Person1
        {
            public int Id { get; set; }
            public string Name { get; set; }
    
            public Person1(int Id,string Name)
            {
                this.Id = Id;
                this.Name = Name;
            }
        }

    测试:

    Person1 p1 = new Person1(1, "ypf1");
    Person1 p2 = new Person1(1, "ypf1");
    Person1 p3 = p1;
    
    Console.WriteLine(p1 == p2);  //false
    Console.WriteLine(p1.Equals(p2)); //false
    
    //下面指向同一个地址
    Console.WriteLine(p1 == p3);  //true
    Console.WriteLine(p1.Equals(p3)); //true

    2. record

      在C#9.0中增加了记录(record)类型的语法,编译器会为我们自动生成Equals、GetHashcode等方法。

      编译器会根据Person类型中的属性定义,自动为Person类型生成包含全部属性的构造方法。注意,默认情况下,编译器会生成一个包含所有属性的构造方法,

      因此,我们编写new Person()、new Person(“ypf”)这两种写法都是不可以的。也会生成ToString方法和Equals等方法

      对于record类型,如果两个两个对象内容完全一样,使用equal或==相比较,返回的是true。如Person2相关案例

    代码分享

     public record Person2(int Id,string Name);

    测试

    {
        Person2 p1 = new(1, "ypf1");
        Person2 p2 = new(1, "ypf1");
        Console.WriteLine(p1 == p2);  //true
        Console.WriteLine(p1.Equals(p2)); //true
    }

     3. 补充

       默认生成的构造方法的行为不能修改,我们可以为类型提供多个构造方法,然后其他构造方法通过this调用默认的构造方法。

       如下Person3案例

    4. 对象的拷贝

       record也是普通类,变量的赋值是引用的传递。

       生成一个对象的副本,这个对象的其他属性值与原对象的相同,只有一个或者少数几个属性改变。麻烦的做法:User u2 = new User(u1.UserName, "test@example", u1.Age);

       简单的方法:用with关键字简化:User u2 = u1 with { Email= “test@example” }; 创建的也是拷贝

    代码分享:

     public record Person3(int Id, string Email, string Name)
        {
            public Person3(int Id, string Name) : this(Id, null, Name)
            {
    
            }
        }

    测试:

    {
        Person3 p1 = new(1, "ypf1");
        Person3 p2 = new(1, "ypf@qq.com", "ypf2");
    
    
        Person3 p3 = p1 with { };
        Console.WriteLine(p1 == p3); //true
        Console.WriteLine(Object.ReferenceEquals(p1, p3)); //比较两个对象是否指向同一个地址, false
    
        Person3 p4 = p1 with { Email = "lmr@qq.com" };
        Console.WriteLine(p1 == p4); //false
        Console.WriteLine(Object.ReferenceEquals(p1, p4)); //比较两个对象是否指向同一个地址, false
    }

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    CentOS6.4 安装nmon
    CentOS6.4 访问域局网中Windows的共享
    将类似 12.56MB 36.89KB 转成 以K为单位的数字【备忘】
    ICE中间件相关
    HDFS介绍
    漫画描述HDFS工作原理
    离线安装Cloudera Manager 5和CDH5
    storm集群相关资料
    kafka相关资料
    jstatd
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/16242476.html
Copyright © 2020-2023  润新知