1. C# 中的動(dòng)態(tài)類型

        共 9072字,需瀏覽 19分鐘

         ·

        2021-01-26 17:22


        翻譯自 Camilo Reyes 2018年10月15日的文章?《Working with the Dynamic Type in C#》?[1]??


        .NET 4 中引入了動(dòng)態(tài)類型。動(dòng)態(tài)對(duì)象使您可以處理諸如 JSON 文檔之類的結(jié)構(gòu),這些結(jié)構(gòu)的組成可能要到運(yùn)行時(shí)才能知道。在本文中,Camilo Reyes 解釋了如何使用動(dòng)態(tài)類型。

        .NET 4.0 中引入的?dynamic?關(guān)鍵字為 C# 編程帶來(lái)了一個(gè)范式轉(zhuǎn)變。對(duì)于 C# 程序員來(lái)說(shuō),強(qiáng)類型系統(tǒng)之上的動(dòng)態(tài)行為可能會(huì)讓人感到不適 —— 當(dāng)您在編譯過(guò)程中失去類型安全性時(shí),這似乎是一種倒退。

        動(dòng)態(tài)編程可能使您面臨運(yùn)行時(shí)錯(cuò)誤。聲明一個(gè)在執(zhí)行過(guò)程中會(huì)發(fā)生變化的動(dòng)態(tài)變量是可怕的,當(dāng)開發(fā)人員對(duì)數(shù)據(jù)做出錯(cuò)誤的假設(shè)時(shí),代碼質(zhì)量就會(huì)受到影響。

        對(duì) C# 程序員來(lái)說(shuō),避免代碼中的動(dòng)態(tài)行為是合乎邏輯的,具有強(qiáng)類型的經(jīng)典方法有很多好處。通過(guò)類型檢查得到的數(shù)據(jù)類型的良好反饋對(duì)于正常運(yùn)行的程序是至關(guān)重要的,一個(gè)好的類型系統(tǒng)可以更好地表達(dá)意圖并減少代碼中的歧義。

        隨著動(dòng)態(tài)語(yǔ)言運(yùn)行時(shí)(Dynamic Language Runtime,DLR)的引入,這對(duì) C# 意味著什么呢?.NET 提供了豐富的類型系統(tǒng),可用于編寫企業(yè)級(jí)軟件。讓我們來(lái)仔細(xì)看看?dynamic?關(guān)鍵字,并探索一下它的功能。

        類型層次結(jié)構(gòu)

        公共語(yǔ)言運(yùn)行時(shí)(Common Language Runtime,CLR)中的每種類型都繼承自?System.Object,現(xiàn)在,請(qǐng)重復(fù)閱讀這句話,直到將其銘記于心。這意味著?object?類型是整個(gè)類型系統(tǒng)的公共父類。當(dāng)我們研究更神奇的動(dòng)態(tài)行為時(shí),這一事實(shí)本身就能為我們提供幫助。這里的想法是開發(fā)這種“代碼感”,以便于您了解如何駕馭 C# 中的動(dòng)態(tài)類型。

        為了演示這一點(diǎn),您可以編寫以下程序:

        Console.WriteLine("long inherits from ValueType: " + typeof(long).IsSubclassOf(typeof(ValueType)));

        我將忽略?using?語(yǔ)句直到本文結(jié)束,以保持對(duì)代碼示例的專注。然后,我再介紹每個(gè)命名空間及其作用。這樣我就不必重復(fù)說(shuō)過(guò)的話,并提供了一個(gè)回顧所有類型的機(jī)會(huì)。

        上面的代碼在控制臺(tái)中的運(yùn)算結(jié)果為?True。.NET 中的?long?類型是值類型,因此它更像是枚舉或結(jié)構(gòu)體。ValueType?重寫來(lái)自?object?類的默認(rèn)行為。ValueType?的子類在棧(stack)上運(yùn)行,它們的生命周期較短,效率更高。

        要驗(yàn)證?ValueType?是繼承自?System.Object?的,請(qǐng)執(zhí)行以下代碼:

        Console.WriteLine("ValueType inherits from System.Object: " + typeof(ValueType).IsSubclassOf(typeof(Object)));

        它的運(yùn)算結(jié)果為?True。這是一條可以追溯到?System.Object?的繼承鏈。對(duì)于值類型,鏈中至少有兩個(gè)父級(jí)。

        再看一下從?System.Object?派生的另一個(gè) C# 類型,例如:

        Console.WriteLine("string inherits from System.Object: " + typeof(string).IsSubclassOf(typeof(Object)));

        此代碼在控制臺(tái)中顯示為?True。另一種從?object?繼承的類型是引用類型,引用類型在堆(heap)上分配并進(jìn)行垃圾回收,CLR 管理著引用類型,并在必要時(shí)從堆中釋放它們。

        查看下圖,您可以直觀地看到 CLR 的類型系統(tǒng):

        值類型和引用類型都是 CLR 的基本構(gòu)建塊,這種優(yōu)雅的類型系統(tǒng)在 .NET 4.0 和動(dòng)態(tài)類型之前就有了。我建議您在使用 C# 中的類型時(shí),在腦海中記住這張圖。那么,DLR 是如何適應(yīng)這張圖的呢?

        動(dòng)態(tài)語(yǔ)言運(yùn)行時(shí)(DLR)

        動(dòng)態(tài)語(yǔ)言運(yùn)行時(shí)(Dynamic Language Runtime, DLR)是處理動(dòng)態(tài)對(duì)象的一種便捷方法。比如,假設(shè)您有 XML 或 JSON 格式的數(shù)據(jù),其中的成員事先并不知道。DLR 允許您使用自然代碼來(lái)處理對(duì)象和訪問(wèn)成員。

        對(duì)于 C#,這使您可以處理在編譯時(shí)不知道其類型的庫(kù)。動(dòng)態(tài)類型消除了自然 API 代碼中的萬(wàn)能字符串。這就開啟了像 IronPython 一樣位于 CLR 之上的動(dòng)態(tài)語(yǔ)言。

        可以將 DLR 視為支持三項(xiàng)主要服務(wù):

        • 表達(dá)式樹,來(lái)自 System.Linq.Expressions 命名空間。編譯器在運(yùn)行時(shí)生成具有動(dòng)態(tài)語(yǔ)言互操作性的表達(dá)式樹。動(dòng)態(tài)語(yǔ)言超出了本文的討論范圍,這里就不作介紹了。

        • 調(diào)用站點(diǎn)緩存,即緩存動(dòng)態(tài)操作的結(jié)果。DLR 緩存像?a + b?之類的操作,并存儲(chǔ)?a?和?b?的特征。當(dāng)執(zhí)行動(dòng)態(tài)操作時(shí),DLR 將檢索先前操作中可用的信息。

        • 動(dòng)態(tài)對(duì)象互操作性是可用于訪問(wèn) DLR 的 C# 類型。這些類型包括?DynamicObject?和?ExpandoObject??捎玫念愋瓦€有很多,但是在處理動(dòng)態(tài)類型時(shí)請(qǐng)注意這兩種類型。

        要了解 DLR 和 CLR 是如何結(jié)合在一起的,請(qǐng)看下圖:

        DLR 位于 CLR 之上?;叵胍幌?,我說(shuō)過(guò)的每種類型都是從?System.Object?派生而來(lái)的。嗯,這句話對(duì)于 CLR 是適用的,但是對(duì)于 DLR 呢?我們使用下面的程序來(lái)測(cè)試一下這個(gè)理論:

        Console.WriteLine("ExpandoObject inherits from System.Object: " + typeof(ExpandoObject).IsSubclassOf(typeof(Object)));

        Console.WriteLine("DynamicObject inherits from System.Object: " + typeof(DynamicObject).IsSubclassOf(typeof(Object)));

        ExpandoObject?和?DynamicObject?在命令行中輸出的值都是?True??梢詫⑦@兩個(gè)類視為使用動(dòng)態(tài)類型的基本構(gòu)建塊,它們清楚地描繪了兩個(gè)運(yùn)行時(shí)是如何結(jié)合在一起的。

        一個(gè) JSON 序列化程序

        動(dòng)態(tài)類型解決的一個(gè)問(wèn)題是,當(dāng)您有一個(gè)不知道其成員的 JSON HTTP 請(qǐng)求時(shí),假設(shè)要在 C# 中使用此任意的 JSON。要解決這個(gè)問(wèn)題,請(qǐng)將此 JSON 序列化為 C# 動(dòng)態(tài)類型。

        我將使用 Newtonsoft 序列化庫(kù),您可以通過(guò) NuGet 添加此依賴項(xiàng),例如:

        dotnet add package Newtonsoft.Json –-version 11.0.2

        您可以使用這個(gè)序列化程序來(lái)處理?ExpandoObject?和?DynamicObject。探索每種動(dòng)態(tài)類型給動(dòng)態(tài)編程帶來(lái)了什么。

        ExpandoObject 動(dòng)態(tài)類型

        ExpandoObject?是一種方便的類型,允許設(shè)置和檢索動(dòng)態(tài)成員。它實(shí)現(xiàn)了?IDynamicMetaObjectProvider,該接口允許在 DLR 中的語(yǔ)言之間共享實(shí)例。因?yàn)樗鼘?shí)現(xiàn)了?IDictionary?和?IEnumerable,所以它也可以處理 CLR 中的類型。舉例來(lái)說(shuō),它允許將?ExpandoObject?的實(shí)例轉(zhuǎn)換為?IDictionary,然后像其它任意的?IDictionary?類型一樣枚舉成員。

        要用?ExpandoObject?處理任意 JSON,您可以編寫以下程序:

        var exObj = JsonConvert.DeserializeObject("{\"a\":1}") as dynamic;

        Console.WriteLine($"exObj.a = {exObj?.a}, type of {exObj?.a.GetType()}");
        //exObj.a = 1, type of System.Int64

        它將會(huì)在控制臺(tái)打印?1?和?long。請(qǐng)注意,盡管它是一個(gè)動(dòng)態(tài) JSON,但它會(huì)綁定到 CLR 中的 C# 類型。由于數(shù)字的類型未知,因此序列化程序默認(rèn)會(huì)選擇最大的?long?類型。注意,我成功地將序列化結(jié)果轉(zhuǎn)換成了具有 null 檢查的?dynamic?類型,其原因是序列化程序返回來(lái)自 CLR 的?object?類型。因?yàn)?ExpandoObject?繼承自?System.Object,所以可以被拆箱成 DLR 類型。

        更奇妙的是,可以用?IDictionary?枚舉?exObj

        foreach (var exObjProp in exObj as IDictionary<string, object> ?? new Dictionary<string, object>())
        {
        Console.WriteLine($"IDictionary = {exObjProp.Key}: {exObjProp.Value}");
        }

        它在控制臺(tái)中輸出?IDictionary = a: 1。請(qǐng)確保使用?string?和?object?作為鍵和值的類型。否則,將在轉(zhuǎn)換的過(guò)程中拋出?RuntimeBinderException?異常。

        DynamicObject 動(dòng)態(tài)類型

        DynamicObject?提供對(duì)動(dòng)態(tài)類型的精確控制。您可以繼承該類型并重寫動(dòng)態(tài)行為。例如,您可以定義如何設(shè)置和獲取類型中的動(dòng)態(tài)成員。DynamicObject?允許您通過(guò)重寫選擇實(shí)現(xiàn)哪些動(dòng)態(tài)操作。這比實(shí)現(xiàn)?IDynamicMetaObjectProvider?的語(yǔ)言實(shí)現(xiàn)方式更易訪問(wèn)。它是一個(gè)抽象類,需要繼承它而不是實(shí)例化它。該類有 14 個(gè)虛方法,它們定義了類型的動(dòng)態(tài)操作,每個(gè)虛方法都允許重寫以指定動(dòng)態(tài)行為。

        假設(shè)您想要精確控制動(dòng)態(tài) JSON 中的內(nèi)容。盡管事先不知道其屬性,您卻可以使用?DynamicObject?來(lái)控制類型。

        讓我們來(lái)重寫三個(gè)方法,TryGetMember、TrySetMember?和?GetDynamicMemberNames

        public class TypedDynamicJson<T> : DynamicObject
        {
        private readonly IDictionary<string, T> _typedProperty;

        public TypedDynamicJson()
        {
        _typedProperty = new Dictionary<string, T>();
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
        T typedObj;

        if (_typedProperty.TryGetValue(binder.Name, out typedObj))
        {
        result = typedObj;

        return true;
        }

        result = null;

        return false;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
        if (value.GetType() != typeof(T))
        {
        return false;
        }

        _typedProperty[binder.Name] = (T)value;

        return true;
        }

        public override IEnumerable<string> GetDynamicMemberNames()
        {
        return _typedProperty.Keys;
        }
        }

        C# 泛型強(qiáng)類型?_typedProperty?以泛型的方式驅(qū)動(dòng)成員類型。這意味著其屬性類型來(lái)自泛型類型?T。動(dòng)態(tài) JSON 成員位于字典中,并且僅存儲(chǔ)泛型類型。此動(dòng)態(tài)類型允許同一類型的同類成員集合。盡管它允許動(dòng)態(tài)成員集,但您可以強(qiáng)類型其行為。假設(shè)您只關(guān)心任意 JSON 中的?long?類型:

        var dynObj = JsonConvert.DeserializeObjectlong>>("{\"a\":1,\"b\":\"1\"}") as dynamic;
        Console.WriteLine($"dynObj.a = {dynObj?.a}, type of {dynObj?.a.GetType()}");

        var members = string.Join(",", dynObj?.GetDynamicMemberNames());
        Console.WriteLine($"dynObj member names: {members}");

        結(jié)果是,您將看到一個(gè)值為?1?的屬性,因?yàn)榈诙€(gè)屬性是?string?類型。如果將泛型類型更改為?string,將會(huì)獲得第二個(gè)屬性。

        類型結(jié)果

        到目前為止,已經(jīng)涉及了相當(dāng)多的領(lǐng)域; 以下是一些亮點(diǎn):

        • CLR 和 DLR?中的所有類型都繼承自?System.Object

        • DLR 是所有動(dòng)態(tài)操作發(fā)生的地方

        • ExpandoObject?實(shí)現(xiàn)了 CLR 中諸如?IDictionary?的可枚舉類型

        • DynamicObject?通過(guò)虛方法對(duì)動(dòng)態(tài)類型進(jìn)行精確控制

        看一下在控制臺(tái)的結(jié)果截圖:

        單元測(cè)試

        對(duì)于單元測(cè)試,我將使用 xUnit 測(cè)試框架。在 .NET Core 中,您可以使用?dotnet new xunit?命令添加一個(gè)測(cè)試項(xiàng)目。一個(gè)顯而易見的問(wèn)題是模擬和驗(yàn)證動(dòng)態(tài)參數(shù),例如,假設(shè)您想驗(yàn)證一個(gè)方法調(diào)用是否具有動(dòng)態(tài)屬性。

        要使用 Moq 模擬庫(kù),您可以通過(guò) NuGet 添加此依賴項(xiàng),例如:

        dotnet add package Moq –-version 4.10.0

        假設(shè)您有一個(gè)接口,其想法是驗(yàn)證它是否被正確的動(dòng)態(tài)對(duì)象調(diào)用。

        public interface IMessageBus
        {
        void Send(dynamic message);
        }

        忽略該接口的實(shí)現(xiàn)。這些實(shí)現(xiàn)細(xì)節(jié)對(duì)于編寫單元測(cè)試不是必需的。下面是被測(cè)試的系統(tǒng):

        public class MessageService
        {
        private readonly IMessageBus _messageBus;

        public MessageService(IMessageBus messageBus)
        {
        _messageBus = messageBus;
        }

        public void SendRawJson<T>(string json)
        {
        var message = JsonConvert.DeserializeObject(json) as dynamic;

        _messageBus.Send(message);
        }
        }

        您可以使用泛型,這樣就可以為序列化程序傳入動(dòng)態(tài)類型。然后調(diào)用?IMessageBus?并發(fā)送動(dòng)態(tài)消息。被測(cè)試的方法接受一個(gè)?string?參數(shù),并使用?dynamic?類型進(jìn)行調(diào)用。

        對(duì)于單元測(cè)試,請(qǐng)將其封裝在?MessageServiceTests?類中。首先初始化 Mock 和被測(cè)試的服務(wù):

        public class MessageServiceTests
        {
        private readonly Mock _messageBus;
        private readonly MessageService _service;

        public MessageServiceTests()
        {
        _messageBus = new Mock();

        _service = new MessageService(_messageBus.Object);
        }
        }

        使用 Moq 庫(kù)中的 C# 泛型來(lái)模擬?IMessageBus,然后使用?Object?屬性創(chuàng)建一個(gè)模擬實(shí)例。在所有的單元測(cè)試中私有實(shí)例變量都很有用,高可重用性的私有實(shí)例增加了類的內(nèi)聚性。

        使用 Moq 驗(yàn)證調(diào)用,一種直觀的方式是嘗試這么做:

        _messageBus.Verify(m => m.Send(It.Is(o => o != null && (o as dynamic).a == 1)));

        但是,遺憾的是,您將看到這樣的錯(cuò)誤消息:“表達(dá)式樹不能包含動(dòng)態(tài)操作?!?這是因?yàn)?C# lambda 表達(dá)式無(wú)法訪問(wèn) DLR,它期望一個(gè)來(lái)自 CLR 的類型,這使得此動(dòng)態(tài)參數(shù)難以驗(yàn)證。記得您的訓(xùn)練,利用您的“代碼感”來(lái)解決這個(gè)問(wèn)題。

        要處理諸如類型之間不一致的問(wèn)題,請(qǐng)使用?Callback?方法:

        dynamic message = null;

        _messageBus.Setup(m => m.Send(It.IsAny())).Callback<object>(o => message = o);

        請(qǐng)注意,Callback?方法將類型轉(zhuǎn)換為?System.Object。因?yàn)樗蓄愋投祭^承自?object?類型,所以可以將其賦值為?dynamic?類型。C# 可以把此 lambda 表達(dá)式中的?object?拆箱成?dynamic message。

        是時(shí)候?yàn)?ExpandoObject?類型編寫一個(gè)漂亮的單元測(cè)試了。使用 xUnit 作為測(cè)試框架,您將看到帶有?Fact?屬性的方法。

        [Fact]
        public void SendsWithExpandoObject()
        {
        // arrange
        const string json = "{\"a\":1}";
        dynamic message = null;

        _messageBus.Setup(m => m.Send(It.IsAny())).Callback<object>(o => message = o);

        // act
        _service.SendRawJson(json);

        // assert
        Assert.NotNull(message);
        Assert.Equal(1, message.a);
        }

        使用?DynamicObject?類型進(jìn)行測(cè)試,重用您之前看到的?TypedDynamicJson

        [Fact]
        public void SendsWithDynamicObject()
        {
        // arrange
        const string json = "{\"a\":1,\"b\":\"1\"}";
        dynamic message = null;

        _messageBus.Setup(m => m.Send(It.IsAnylong>>())).Callback<object>(o => message = o);

        // act
        _service.SendRawJsonlong>>(json);

        // assert
        Assert.NotNull(message);
        Assert.Equal(1, message.a);
        Assert.Equal("a", string.Join(",", message.GetDynamicMemberNames()));
        }

        使用 C# 泛型,您可以在重用代碼的同時(shí)轉(zhuǎn)換序列化程序的動(dòng)態(tài)類型。Moq 中的?Callback?方法允許您在兩種類型系統(tǒng)之間進(jìn)行必要的跳轉(zhuǎn)。擁有一個(gè)優(yōu)雅的類型層次結(jié)構(gòu)和一個(gè)共同的父類成為了一個(gè)救星。

        Using 語(yǔ)句

        下面的 using 語(yǔ)句是代碼示例的一部分:

        • System: CLR 的基礎(chǔ)類型,例如 Object 和 Console

        • System.Collections.Generic: 可枚舉類型,例如 IDictionary

        • System.Dynamic: DLR 的動(dòng)態(tài)類型,例如 ExpandoObject 和 DynamicObject

        • Newtonsonft.Json: JSON 序列化程序

        • Moq: 模擬庫(kù)

        • Xunit: 測(cè)試框架

        總結(jié)

        C# 動(dòng)態(tài)類型或許看起來(lái)令人望而生畏,但它在強(qiáng)類型系統(tǒng)之上有很多好處。DLR 是所有動(dòng)態(tài)操作發(fā)生和與 CLR 交互的地方,類型繼承使同時(shí)處理這兩個(gè)類型系統(tǒng)變得容易。在 C# 中,動(dòng)態(tài)和靜態(tài)編程之間并沒(méi)有對(duì)立,這兩種類型系統(tǒng)共同協(xié)作,以創(chuàng)造性的方式解決動(dòng)態(tài)問(wèn)題。


        相關(guān)鏈接:

        1. https://www.red-gate.com/simple-talk/dotnet/c-programming/working-with-the-dynamic-type-in-c/?Working with the Dynamic Type in C#???


        作者 :Camilo Reyes
        譯者 :技術(shù)譯民?
        出品 :技術(shù)譯站(https://ITTranslator.cn/





        往期精彩回顧




        【推薦】.NET Core開發(fā)實(shí)戰(zhàn)視頻課程?★★★

        .NET Core實(shí)戰(zhàn)項(xiàng)目之CMS 第一章 入門篇-開篇及總體規(guī)劃

        【.NET Core微服務(wù)實(shí)戰(zhàn)-統(tǒng)一身份認(rèn)證】開篇及目錄索引

        Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)

        .NET Core中的一個(gè)接口多種實(shí)現(xiàn)的依賴注入與動(dòng)態(tài)選擇看這篇就夠了

        10個(gè)小技巧助您寫出高性能的ASP.NET Core代碼

        用abp vNext快速開發(fā)Quartz.NET定時(shí)任務(wù)管理界面

        在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務(wù)輕松實(shí)現(xiàn)作業(yè)調(diào)度

        現(xiàn)身說(shuō)法:實(shí)際業(yè)務(wù)出發(fā)分析百億數(shù)據(jù)量下的多表查詢優(yōu)化

        關(guān)于C#異步編程你應(yīng)該了解的幾點(diǎn)建議

        C#異步編程看這篇就夠了


        瀏覽 59
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 狠狠色噜噜色狠狠狠综合久久成人 | 久久婷婷六月综合 | 艳妇臀荡h乳欲伦交换漫画 | 国产成人小视频在线观看 | 在线观看性爱视频 |