c#新语法

顶级语句(c#9.0)

  1. 程序入口处直接写代码,不用类,不用main方法,即:
Console.WriteLine("Hello, World!");

经典写法仍然支持

namespace A
{
	class Program
	{
	  static void Main(string[] args)
	  {
	    Console.WriteLine("Hello, World!");
	  }
	}
}
  1. 同一个项目中只能有一个文件有顶级语句
    如果我们在项目中再添加一个类并且使用顶级语句,会报错
    在这里插入图片描述
  2. 顶级语句中可以用await语法
    之前我们在main方法中如果用await 调用异步方法,main方法前必须加上asyn,返回值也必须为Task,如下所示
 static async Task Main(string[] args)

但是在顶级语句中,可以直接调用异步方法

string s = await File.ReadAllTextAsync("d/:1.txt");
  1. 也可以声明函数
Console.WriteLine(Add(3, 4));// 调用方法

Console.ReadKey();

int Add(int a, int b) // 方法前不能有任何访问修饰符,否则报错
{
    return a + b;
}

运行结果:
在这里插入图片描述
这里面有个需要注意的点: add 方法前面不能有任何访问修饰符,否则会报错。

2 全局using指令(c#10)

  1. 将global添加到using前,这个命名空间就能被整个项目引用,不用重复添加
    假如有这样一个case,animal 类在linq学习命名空间下,但是我想在位于新语法学习命名空间下的program类和dog类中用,那么我必须在两个类中都引入 using linq学习 才可以使用,代码实例如下:
// animal类
namespace linq学习
{
    public class Animal
    {
    }
}

// program类
using linq学习;

Animal animal = new Animal(); // 调用位于linq学习命名空间下的animal类

//dog 类
using linq学习;

namespace 新语法学习
{
    public class Dog
    {
        public Dog()
        {
            Animal animal = new Animal(); //调用位于linq学习命名空间下的animal类
        }
    }
}

现在我将global加到 program类的using linq学习 前,则dog类就不必再次引入了。

  1. 通常新增一个专门用来写全局using指令的代码文件
    新加一个类,将using全部放到这个类里面即可。
    在这里插入图片描述
  2. 如果csproj文件中启用了<ImplicitUsings>enable</ImplicitUsings>,编译器会隐式增加对于system,system.linq等常用命名空间的引入。不同各类型项目引入的命名空间也不同。
    项目文件中,默认是ennable
    在这里插入图片描述

3 USING声明(c#8)

实现了IDisposable接口的对象可以用using来管理,但如果一个类里面有很多非托管资源,就会出现多个using嵌套的情况,如:

            using (var con = new SqlConnection())
            {
                con.Open();
                using (var cmd = new SqlCommand())
                {
                    cmd.CommandText = "select username from user";
                    using (var reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {

                        }
                    }                
                }
            }

从c#8开始引入了using声明。在实现了IDisposable/IAsynDIsposable接口的类型变量前加上using,当代码执行到离开变量的作用域时,对象就会被释放。上面的代码就可以写成:

using var con = new SqlConnection();
con.Open();
using var cmd = new SqlCommand();
cmd.CommandText = "select username from user";
using var reader = cmd.ExecuteReader()while (reader.Read())
{
}                     

我们来看个列子,能更好的理解变量作用域的含义

void TestUsing()
{
    for (int i = 0; i < 3; i++)
    {
        using MyClass myClass = new MyClass();
        Console.WriteLine($"i={i}");
    }

    Console.WriteLine("TestUsing end");
}

    public class MyClass : IDisposable
    {
        public void Dispose()
        {
            Console.WriteLine("myclass disposed.");
        }
    }

在这里插入图片描述
例子中myclass的作用域就是for循环的花括号,所以每次打印过i的值后就会dispose。

4 文件范围的命名空间声明(c#10)

之前c#类的样子,类必须在namespace里面,如下所示:

namespace A
{
	 public class B
	 {
	 }
}

现在可以这样写

namespace A;
public class B
{
}

class和namespace是平级的,但是这个类仍然属于这个命名空间。

5 可空引用类型(c#8)

c#中数据类型分为值类型和引用类型。值类型不可为null,引用类型可为null。如果不注意检查引用类型是否为null,就有可能引起NullReferenceException。为了规避这种异常,引入了可空引用类型。

  1. csproj文件中默认启用<Nullable>enable</Nullable>可空引用类型检查
  2. 在引用类型后添加"?“修饰符来声明这个类型是可空的。对于没有添加”?"修饰符的变量,如果赋值为null,编译器会给出警告信息
namespace 新语法学习
{
    public class Person
    {
        // name addressa 都不可以为null,但是我只给name赋值,address存在为null的可能
        public Person(string name) 
        {
            this.Name = name;
        }
        public string Name { get; set; }
        public string Address { get; set; }
    }
}

编译器会给出警告信息
在这里插入图片描述

6 Init 语法

如果类中的属性只能被当前类赋值,那么我们可以将它的属性设置成 private set,这样在类外就不能给它赋值了。

    public class Person
    {
        public Person(string name, string address)
        {
            this.Name = name;
            this.Address = address;
        }

        public string Name { get;  private set; } // privare set 说明是只能在当前类中赋值

        public string Address { get; set; }
        
        void Test()
        {
            this.Name = "hahha"; // 在当前类中是可以给name赋值的
        }
    }
    // 在program 类中给name赋值 会报错
	Person p1 = new Person("a", "aa");
	p1.Address = "new";
	p1.Name = "ddd"; // name是private set 不能在它所在的类外给它赋值

在这里插入图片描述
如果只想让属性在初始化时被赋值,那么就可以用init

   public class Person
    {

        public Person(string name, string address, string hobby)
        {
            this.Name = name;
            this.Address = address;
            this.Hobby = hobby;
        }

        public string Name { get; private set; } // privare set 说明是只能在当前类中赋值

        public string Address { get; set; }

        public string Hobby { get; init; } // init说明只能在初始化时被赋值

        void Test1()
        {
            this.Name = "hahha"; // 在当前类中是可以给name赋值的
        }

        void Test2()
        {
            this.Hobby = "uuuu"; // 在当前类中是不可以给hobby赋值的,只能在初始化时给它赋值
        }
    }

在person类中增加一个属性hobby,它是init的,然后尝试在person类中写个方法给他赋值,会报错。在program中给它赋值也会报同样的错。
在这里插入图片描述

7 record 类型(c#9)

c#中==默认比较的是两个对象是否指向同一个引用,即使两个对象内容完全相同也不想等。可以通过重写Equal方法来解决,但是需要额外写很多代码。
例:

Person p1 = new Person("a", "aa", "dd");
Person p2 = new Person("a", "aa", "dd");
Console.WriteLine(p1 == p2); // false p1 p2 虽然内容完全相同,但2个对象,指向不同的引用
Console.WriteLine(p1.ToString());// 默认的tostring方法打印的是 命名空间.类名

在这里插入图片描述
在c#9中,新增了record类型的语法,编译器会自动帮我们生成Equals, ToString, GetHashcode等方法。
语法: recode className(datatype property);
新增一个 record 类型的cat类

using System;
namespace 新语法学习
{
    public record Cat(string name, string color);
}

// program 中调用
Cat cat1 = new Cat("a", "blue");
Cat cat2 = new Cat("a", "blue");
Console.WriteLine(cat1 == cat2);
Console.WriteLine(cat1.ToString());

在这里插入图片描述
结果可知,它比较的是对象的内容,并且重写了tostring方法。

原理探究

编译器会根据cat类中的属性定义,自动为cat生成包含所有属性的构造函数。所以我们写 new cat(), new cat(“a”)是不对的。也会自动生成tostring equals等方法。
用反编译器ILSpy去看一下,其实record就是一个普通的类。

深入理解

  1. 可以实现部分属性只读而部分属性可读写。代码示例如下:
namespace 新语法学习
{
    public record Tiger(string name, string color) // name color只能通过构造函数赋值
    {
        public int Age { get; set; } // age可以在外部赋值
    }
}

// program 中调用
Tiger t1 = new Tiger("t", "e");
t1.Age = 2;

Tiger t2 = new Tiger("t", "e");
t2.Age = 3;
Console.WriteLine(t1 == t2);

t2.Age = 2;
Console.WriteLine(t1 == t2);
Console.WriteLine(t1.ToString());

在这里插入图片描述
2. 默认生成的构造函数行为不能更改,但是我们可以为类提供多个构造函数,用this调用默认构造函数

namespace 新语法学习
{
    public record Tiger(string name, string color) // name color只能通过构造函数赋值
    {
        public int Age { get; set; } // age可以在外部赋值

        public Tiger(string name, string color, int age) : this(name, color) // 可以提供多个构造函数
        {
            this.Age = age;
        }
    }
}
  1. 推荐使用只读类型的属性。所有属性都为只读的类型叫做不可变类型,可以让程序逻辑简单,减少并发访问,状态管理等麻烦。
  2. record是普通类,变量的传递是引用传递
// record 是引用传递
Tiger t1 = new Tiger("t", "e");
Tiger t2 = t1;
Console.WriteLine(object.ReferenceEquals(t1, t2)); // 看是不是引用同一个实例

在这里插入图片描述

with 创建对象副本

生成一个对象的副本

// 创建对象副本
Tiger t1 = new Tiger("t", "e");
Tiger t2 = new Tiger(t1.name, t1.color); // 麻烦的写法
Console.WriteLine(t1 == t2);
Console.WriteLine(object.ReferenceEquals(t1, t2)); // 看是不是引用同一个实例

在这里插入图片描述

with写法:

Tiger t1 = new Tiger("t", "e");
//Tiger t2 = new Tiger(t1.name, t1.color); // 麻烦的写法
Tiger t2 = t1 with { }; // with 写法
Console.WriteLine(t1 == t2);
Console.WriteLine(object.ReferenceEquals(t1, t2)); // 看是不是引用同一个实例

在这里插入图片描述

// 创建对象副本
Tiger t1 = new Tiger("t", "e");
Tiger t3 = t1 with { name = "t3" };// 只有name不同
Console.WriteLine(t3.ToString());

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值