在《带你了解C#每一个版本新特性》 一文中介绍了,C# 1.0 到 7.0 的不一样特性,本文接着介绍在 8.0 和 9.0 中的一些经常使用新特性。
sql
在 dotNET Core 3.1 及以上版本中就可使用 C# 8 的语法,下面是 C# 8 中我认为比较经常使用的一些新功能。ide
接口是用来约束行为的,在 C# 8 之前,接口中只能进行方法的定义,下面的代码在 C# 8 之前是会报编译错误的:svg
public interface IUser
{
string GetName() => "oec2003";
}
那么在 C# 8 中,能够正常使用上面的代码,也就是说能够对接口中的方法提供默认实现。post
接口默认方法最大的好处是,当在接口中进行方法扩展时,以前的实现类能够不受影响,而在 C# 8 以前,接口中若是要添加方法,全部的实现类须要进行新增接口方法的实现,不然编译失败。优化
C# 中不支持多重继承,主要的缘由是会致使菱形问题:url
当调用类 D 的 Test 方法时,就不知道应该使用 B 的 Test 仍是 C 的 Test,这个就是菱形问题。spa
而接口是容许多继承的,那么当接口支持默认方法时,是否也会致使菱形问题呢?看下面代码:3d
public interface IA
{
void Test() => Console.WriteLine("Invoke IA.Test");
}
public interface IB:IA
{
void Test() => Console.WriteLine("Invoke IB.Test");
}
public interface IC:IA
{
void Test() => Console.WriteLine("Invoke IC.Test");
}
public class D : IB, IC { }
static void Main(string[] args)
{
D d = new D();
d.Test();
}
上面的代码是没法经过编译的,由于接口的默认方法不能被继承,因此类 D 中没有 Test 方法能够调用,以下图:code
因此,必须经过接口类型来进行相关方法的调用:orm
static void Main(string[] args)
{
IA d1 = new D();
IB d2 = new D();
IC d3 = new D();
d1.Test(); // Invoke IA.Test
d2.Test(); // Invoke IB.Test
d3.Test(); // Invoke IC.Test
}
也正是由于必须经过接口类型来进行调用,因此也就不存在菱形问题。而当具体的类中有对接口方法实现的时候,就会调用类上实现的方法:
public interface IA
{
void Test() => Console.WriteLine("Invoke IA.Test");
}
public interface IB:IA
{
void Test() => Console.WriteLine("Invoke IB.Test");
}
public interface IC:IA
{
void Test() => Console.WriteLine("Invoke IC.Test");
}
public class D : IB, IC
{
public void Test() => Console.WriteLine("Invoke D.Test");
}
static void Main(string[] args)
{
IA d1 = new D();
IB d2 = new D();
IC d3 = new D();
d1.Test(); // Invoke D.Test
d2.Test(); // Invoke D.Test
d3.Test(); // Invoke D.Test
}
类可能同时继承类和接口,这时会优先调用类中的方法:
public class A
{
public void Test() => Console.WriteLine("Invoke A.Test");
}
public interface IA
{
void Test() => Console.WriteLine("Invoke IA.Test");
}
public class D : A, IA { }
static void Main(string[] args)
{
D d = new D();
IA d1 = new D();
d.Test(); // Invoke A.Test
d1.Test(); // Invoke A.Test
}
关于默认接口方法,总结以下:
咱们都知道 using 关键字能够导入命名空间,也能定义别名,还能定义一个范围,在范围结束时销毁对象,在 C# 8.0 中的 using 变量声明可让代码看起来更优雅。
在没有 using 变量声明的时候,咱们是这样使用的:
static void Main(string[] args)
{
var connString = "Host=221.234.36.41;Username=gpadmin;Password=123456;Database=postgres;Port=54320";
using (var conn = new NpgsqlConnection(connString))
{
conn.Open();
using (var cmd = new NpgsqlCommand("select * from user_test", conn))
{
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
Console.WriteLine(reader["user_name"]);
}
}
}
Console.ReadKey();
}
当调用层级比较多时,会出现 using 的嵌套,对影响代码的可读性,固然,当两个 using 语句中间没有其余代码时,能够这样来优化:
static void Main(string[] args)
{
var connString = "Host=221.234.36.41;Username=gpadmin;Password=123456;Database=postgres;Port=54320";
using (var conn = new NpgsqlConnection(connString))
{
conn.Open();
using (var cmd = new NpgsqlCommand("select * from user_test", conn))
using (var reader = cmd.ExecuteReader())
while (reader.Read())
Console.WriteLine(reader["user_name"]);
}
Console.ReadKey();
}
使用 using 变量声明后的代码以下:
static void Main(string[] args)
{
var connString = "Host=221.234.36.41;Username=gpadmin;Password=123456;Database=postgres;Port=54320";
using var conn = new NpgsqlConnection(connString);
conn.Open();
using var cmd = new NpgsqlCommand("select * from user_test", conn);
using var reader = cmd.ExecuteReader();
while (reader.Read())
Console.WriteLine(reader["user_name"]);
Console.ReadKey();
}
这是一个颇有用的语法糖,在 C# 中若是调用一个为 Null 的引用类型上的方法,会出现经典的错误:”未将对应引用到对象的实例“,因此咱们在返回引用类型时,须要作些判断:
static void Main(string[] args)
{
List<string> list = GetUserNames();
if(list==null)
{
list = new List<string>();
}
Console.WriteLine(list.Count);
}
public static List<string> GetUserNames()
{
return null;
}
在 C# 8 中可使用 ??= 操做符更简单地实现:
static void Main(string[] args)
{
List<string> list = GetUserNames();
list ??= new List<string>();
Console.WriteLine(list.Count);
}
当 list 为 null 时,会将右边的值分配给 list 。
在 .NET 5 中可使用 C# 9 ,下面是 C# 9 中几个经常使用的新特性。
init 是属性的一种修饰符,能够设置属性为只读,但在初始化的时候却能够指定值:
public class UserInfo
{
public string Name { get; init; }
}
UserInfo user = new UserInfo { Name = "oec2003" };
//当 user 初始化完了以后就不能再改变 Name 的值
user.Name = "oec2004";
上面代码中给 Name 属性赋值会出现编译错误:
在 C# 9 中新增了 record 修饰符,record 是一种引用类型的修饰符,使用 record 修饰的类型是一种特别的 class,一种不可变的引用类型。
咱们建立一个名为 UserInfo 的 class ,不一样的实例中即使属性值彻底相同,这两个实例也是不相等的,看下面代码:
public class UserInfo
{
public string Name { get; set; }
}
static void Main(string[] args)
{
UserInfo user1 = new UserInfo { Name = "oec2003" };
UserInfo user2 = new UserInfo { Name = "oec2003" };
Console.WriteLine(user1== user2); //False
}
若是使用 record ,将会看到不同的结果,由于 record 中重写了 ==、Equals 等 ,是按照属性值的方式来进行比较的:
public record UserInfo
{
public string Name { get; set; }
}
static void Main(string[] args)
{
UserInfo user1 = new UserInfo { Name = "oec2003" };
UserInfo user2 = new UserInfo { Name = "oec2003" };
Console.WriteLine(user1== user2); //True
}
在 class 中咱们常常将一个对象的实例赋值给另外一个值,对赋值后的对象实例进行属性值的改变会影响到原对象实例:
public class UserInfo
{
public string Name { get; set; }
}
static void Main(string[] args)
{
UserInfo user = new UserInfo { Name = "oec2003" };
UserInfo user1 = user;
user1.Name = "oec2004";
Console.WriteLine(user.Name); // oec2004
}
若是想要不影响原对象实例,就须要使用到深拷贝,在 record 中,可使用 with 语法简单地达到目的:
public record UserInfo
{
public string Name { get; set; }
}
static void Main(string[] args)
{
UserInfo user = new UserInfo { Name = "oec2003" };
UserInfo user1 = user with { Name="eoc2004"};
Console.WriteLine(user.Name); // oec2003
Console.WriteLine(user1.Name); // oec2004
}
模式匹配中我以为最有用的就是对 Null 类型的判断,在 9.0 中支持这样的写法了:
public static string GetUserName(UserInfo user)
{
if(user is not null)
{
return user.Name;
}
return string.Empty;
}
这个不知道有啥用?但挺好玩的,建立一个控制台程序,将 Program.cs 中的内容替换为下面这一行,程序也能正常运行:
System.Console.WriteLine("Hello World!");
除此以外,在 C# 8.0 和 9.0 中还有一些其余的新功能,我目前没有用到或者我以为不太经常使用,就没有写在本文中了。
但愿本文对您有所帮助。