歡迎光臨
每天分享高質量文章

C# 8.0 兩個有趣的新特性以及gRPC

作者:雨少主

連結:https://zhuanlan.zhihu.com/p/63779162

關於C#語法特性的部分需要Visual Studio 2019支援。

 

關於.NET Core的部分需要安裝.NET 3.0 Preview4,低版本或許也可以但我沒實驗。

 

如果要在最新版的VS2019中使用.NET 3.0,可能需要在 選項 – 解決方案與專案- ASP.NET Core 中啟用 使用 .NET Core SDK 預覽版 選項。

C# 8.0新特性:可空的取用型別

static void Main(string[] args)
{
#nullable enable
    string a = null;
    string? b = null;
    var c = a.Length;
    var d = b.Length;
    var e = b!.Length;
#nullable disable
    string? f = null;
}

 

複製以上簡單的程式碼到IDE就能展現這個特性的特點與用法:

 

  • IDE會對 a 賦值為 null 的操作進行警告, 因為在約定中 a 不可為空,而 b 則不會警告,因為它可以為 null ;

  • IDE會對 a.Length 的訪問進行警告,因為已經靜態推斷出 a 為 null 了;

  • IDE會對 b.Length 的訪問進行警告,b 型別可能為空;

  • b!.Length 的訪問操作不會被警告,因為這種形式的訪問表示老子已經知道它可能為 null 了你閉嘴;

  • string? f =null 陳述句會被IDE警告,因為上面已經把可為空的取用型別特性關閉了。

 

另外此特性不止支援 enable 和 disable 選項,還支援 restore 還原之前的設定,以及透過 safeonly 或 warnings 設定“定製”啟用警告的範圍,具體可參照其 詳細說明 。

 

我們可以發現這個特性的的實質其實是一個“柔性”斷言,啟用後IDE會對部分程式碼進行警告提示,督促我們進行處理,但也止於此了。它非常靈活,新專案啟用此特性是值得的,但舊專案也沒必要升級。

C# 8.0新特性:using 宣告

這裡可以直接看官網的例子:

static void WriteLinesToFile(IEnumerable<string> lines)
{
    using var file = new System.IO.StreamWriter("WriteLines2.txt");
    foreach (string line in lines)
    {
        // If the line doesn't contain the word 'Second', write the line to the file.
        if (!line.Contains("Second"))
        {
            file.WriteLine(line);
        }
    }
// file is disposed here
}

 

等價於:

static void WriteLinesToFile(IEnumerable<string> lines)
{
    using (var file = new System.IO.StreamWriter("WriteLines2.txt"))
    {
        foreach (string line in lines)
        {
            // If the line doesn't contain the word 'Second', write the line to the file.
            if (!line.Contains("Second"))
            {
                file.WriteLine(line);
            }
        }
    } // file is disposed here
}

 

也就是說使用 using 關鍵字修飾的變數宣告,它在作用域結束後會自動釋放。一開始我沒明白這個有什麼意義,今天和 @EanCuznaivy談到某種情況,就是某些型別之所以會繼承 IDispose 介面,可能是基於對語意或設計實現上的軟需求,並非它一定需要呼叫 Dispose 方法才能夠釋放

(比如 ProcessModule Class (System.Diagnostics) )。

 

在這種情況下,對於我這樣的強迫症患者而言,明知道沒必要,但也得不厭其煩地 try finally 或者 using{}。有了這個特性,在寫類似的程式碼的時候,可以只多加幾個字就讓心情舒暢,是強迫症患者的福音。

 

另外在進行一些很常見的操作比如IO(Stream)、摘要計算(HashAlgorithm)時,可以少寫一些程式碼。

ASP dot NET Core 3.0中的 gRPC 服務

.NET CORE使用gRPC服務需要用到兩個Nuget包:

 

  • 執行時:Google.Protobuf

  • 支援套件:Grpc.Tools

 

對於客戶端而言,還需要 Grpc.Core 包的支援。

 

Google.Protobuf 不必解釋,Grpc.Core 是一系列客戶端要用到的API,而 Grpc.Tools 的牛逼之處在於不用編譯 *.proto 檔案即可直接在C#中取用它……

 

對於.NET Core 2.1 或 2.2而言使用 gPRC 服務還需要手寫微量程式碼(XXX.BindService方法),而到了.NET CORE 3.0,取用 Grpc.AspNetCore.Server 包後即可直接以慣常的配置方式(AddXXX)直接使用此服務。

 

這裡偷個懶,直接用 Visual Studio 2019+.NET CORE 3.0做示例。VS 2019中有 gRPC 伺服器的模板,選擇後直接會建立一個現成的新手示例。

 

我們一定會註意到 Startup 類中 ConfigureServices 方法的陳述句 services.AddGrpc() 。

 

這個是慣例,不用去管,重點看 Configure 方法裡的程式碼片段:

app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapGrpcService();
});

 

此處和 WCF 的思想類似,將服務新增到路由終結點,讓客戶端連線。

 

然後可以看位於 Protos 檔案夾下的 greet.proto 檔案:

syntax = "proto3";

package Greet;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings.
message HelloReply {
  string message = 1;
}

 

一個最簡單的rpc伺服器。

 

然後再看 Services 檔案夾下的 GreeterService.cs 檔案:

using System.Threading.Tasks;
using Greet;
using Grpc.Core;

namespace GrpcService
{
    public class GreeterService : Greeter.GreeterBase
    {
        public override Task SayHello(HelloRequest request, ServerCallContext context)
        {
            return Task.FromResult(new HelloReply
            {
                Message =$"Hello { context.Method} " + request.Name
            });
        }
    }
}

 

程式碼的實現思路很好理解。我們可以註意到我們能夠直接匯入 Greet 名稱空間,這是因為它已經被 Grpc.Tools 生成到了專案下 obj 檔案夾的專案快取中。

 

最後的一個重點在專案配置檔案(*.csproj)中的 ItemGroup 節點:

<Protobuf Include="Protosgreet.proto" GrpcServices="Server" Generator="MSBuild:Compile" />

 

這就是在專案中取用proto檔案的方法,具體細節詳見官方說明:gRPC services with C# 。

 

然後我們可以建立個客戶端嘗試與服務端通訊,建立一個命令列程式,取用 Google.Protobuf、Grpc.Tools 以及 Grpc.Core 包,同時在專案配置檔案中的 ItemGroup 節點中加入一句話:

<Protobuf Include="..GrpcServiceProtosgreet.proto" GrpcServices="Client" />

 

(我是在服務端專案同目錄建立的客戶端專案,所以路徑直接這麼寫就OK)

 

然後我們可以直接寫:

using System;
using System.Threading.Tasks;
using Greet;
using Grpc.Core;

namespace ConsoleApp1
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var channel = new Channel("localhost:50051", ChannelCredentials.Insecure);
            var client = new Greeter.GreeterClient(channel);
            var reply = await client.SayHelloAsync(
                                          new HelloRequest { Name = "GreeterClient" });
            Console.WriteLine("Greeting: " + reply.Message);
            await channel.ShutdownAsync();
            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }
    }
}

 

建立頻道——建立連線——傳送請求——關閉頻道,簡單易懂。我們著重看兩點。

 

其一是 await channel.ShutdownAsync();:

 

在程式退出前,最好或者說必須關閉曾經建立過的頻道。

 

另一個就是我們會註意到此處程式碼中的Greeter 類所公開的介面完全是面向客戶端的。

 

而同理,上面伺服器中的 Greeter 類公開的介面則是面向伺服器的,這是受專案配置中 GrpcServices=Client|Server的影響,非常智慧化……

贊(0)

分享創造快樂