C#: 8.0 和 9.0 經常使用新特性

圖片

在《帶你瞭解C#每一個版本新特性》 一文中介紹了,C# 1.0 到 7.0 的不一樣特性,本文接着介紹在 8.0 和 9.0 中的一些經常使用新特性。
sql

C# 8.0

在 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

圖片

  • 類 A  是一個抽象類,定義有一個 方法 Test;
  • 類 B 和 類 C 繼承自抽象類 A,並有各自的實現;
  • 類 D 同時繼承類 B 和類 C;

當調用類 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 : IBIC { }

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 : IBIC 
{
    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 : AIA { }
static void Main(string[] args)
{
    D d = new D();
    IA d1 = new D();
    d.Test();  // Invoke A.Test
    d1.Test();  // Invoke A.Test
}

關於默認接口方法,總結以下:

  • 默認接口方法可讓咱們在往底層接口中擴展方法的時候變得比較平滑;
  • 默認方法,會優先調用類中的實現,若是類中沒有實現,纔會去調用接口中的默認方法;
  • 默認方法不可以被繼承,當類中沒有本身實現的時候是不能從類上直接調用的。

using 變量聲明

咱們都知道 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();
}

Null 合併賦值

這是一個頗有用的語法糖,在 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<stringGetUserNames()
{
    return null;
}

在 C# 8 中可使用 ??= 操做符更簡單地實現:

static void Main(string[] args)
{
    List<string> list = GetUserNames();
    list ??= new List<string>();
    Console.WriteLine(list.Count);
}

當 list 爲 null 時,會將右邊的值分配給 list 。

C# 9.0

在 .NET 5 中可使用 C# 9 ,下面是  C# 9 中幾個經常使用的新特性。

init

init 是屬性的一種修飾符,能夠設置屬性爲只讀,但在初始化的時候卻能夠指定值:

public class UserInfo
{
    public string Name { get; init; }
}
UserInfo user = new UserInfo { Name = "oec2003" };
//當 user 初始化完了以後就不能再改變 Name 的值
user.Name = "oec2004";

上面代碼中給 Name 屬性賦值會出現編譯錯誤:

圖片

record

在 C# 9 中新增了 record 修飾符,record 是一種引用類型的修飾符,使用 record 修飾的類型是一種特別的 class,一種不可變的引用類型。

咱們建立一個名爲 UserInfo 的 class ,不一樣的實例中即使屬性值徹底相同,這兩個實例也是不相等的,看下面代碼:

public class UserInfo
{
    public string Name { getset; }
}
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 { getset; }
}
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 { getset; }
}
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 { getset; }
}
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 中還有一些其餘的新功能,我目前沒有用到或者我以爲不太經常使用,就沒有寫在本文中了。

但願本文對您有所幫助。 

相關文章
相關標籤/搜索