天天看點

C#動态類型ExpandoObject,DynamicObject和dynamic傻傻分不清

作者:老白說IT

在 C# 中,dynamic關鍵字、ExpandoObject 類和 DynamicObject 類是處理動态對象的主要方法。但是,ExpandoObject 和 DynamicObject 通常可以互換使用,是以很難區分它們。在本文中,我們将研究ExpandoObject,DynamicObject和dynamic之間的差別。我們将更好地了解這些概念以及如何在代碼中有效地使用它們。

C# 中什麼是dynamic

dynamic是一種 C# 靜态類型。它在 C# 中被引入是為了在比如使用動态類型語言(如 Python)、使用 COM 對象、通路 HTML DOM 或使用 JSON 時提供互操作性(interoperability,互操作性一般指兩種不同語言在同一個系統中的互相互動)。在處理dynamic類型的對象時,我們可以嘗試通路該類型的任何成員,即使它不存在,并且在我們運作代碼之前編譯器也不會顯示任何錯誤。

讓我們建立一個包含一個成員的對象:

dynamic account = new
{
		Name = "Test"
};           

之後,讓我們聲明一個新變量passage并将 account對象的Password值配置設定給它:

var password = account.Password;           

到目前為止,代碼不會引發任何異常,但是當我們運作應用程式時,我們會得到一個RuntimeBinderException。這是因為account對象沒有名為Password的成員。

此外,我們可以為動态變量配置設定任何類型的值:

dynamic count = 1;
Assert.IsType<int>(count);           

在此方法中,我們聲明count為dynamic變量,并為其指派1。然後,在運作時,count的類型将是 int.這意味着動态對象将采用配置設定給它們的值的類型。

動态變量在我們的應用程式中具有靈活性,因為編譯器不會檢查變量。但是,使用動态變量可能會導緻我們的應用程式中出現錯誤。例如,如果我們拼錯變量或嘗試通路不存在的變量,應用程式會抛出一個RuntimeBinderException,因為在編譯時沒有靜态檢查。

什麼是 ExpandoObject?

C# 中的ExpandoObject允許我們建立執行個體,并且在運作時動态添加或删除執行個體的變量。除了添加變量之外,還使我們能夠設定和擷取執行個體變量的值。

要建立ExpandoObject的執行個體,我們需要使用關鍵字dynamic。由于我們需要動态地将變量添加到 ExpandoObject,是以我們需要能夠調用其中不存在的成員而不會出現編譯錯誤,這就是 dynamic 關鍵字有用的地方。

讓我們建立一個 ExpandoObject 的執行個體:

dynamic article = new ExpandoObject();
article.Author = "Laobai";
article.Year = 2023;
Assert.Equal("Laobai", article.Author);
Assert.Equal(2023, article.Year);           

使用點号運算符(.),我們向對象article添加兩個帶有值的新屬性。然後,我們隻需再次使用點号運算符來通路這些值。

另外再讓我們看看如何向 ExpandoObject 添加方法:

article.read = 1000;
article.Read = (Action)(() => { article.read++; });
article.Read();           

我們首先添加值1000的屬性read。然後,我們添加Read為委托函數,此方法僅進行read的遞增。

調用此方法時,每次read的值都會增加1。

周遊ExpandoObject成員

ExpandoObject類實作了接口IDictionary<string, object>。這意味着每次我們向對象添加新成員時,它們都會存儲為鍵值對。

為了擷取ExpandoObject執行個體的所有值,我們周遊對象:

dynamic country = new ExpandoObject();
country.Name = "China";
country.Population = "1.4 Billion people";
foreach (KeyValuePair<string, object> keyValuePair in country)
{
		_testOutputHelper.WriteLine(#34;{keyValuePair.Key} : {keyValuePair.Value}");
}           

我們使用一個循環,用于擷取動态對象的值。然後,我們将結果列印到控制台。

當我們運作此方法時,我們得到:

Name : China
Population : 1.4 Billion people           

現在我們已經學習了如何從 ExpandoObject 中添加和讀取值,讓我們看看如何從對象中删除屬性。

從ExpandoObject執行個體中删除屬性

讓我們建立一個新的ExpandoObject執行個體并從中删除一個屬性:

dynamic person = new ExpandoObject();
person.Age = 30;
person.Name = "Laobai";
((IDictionary<string, object>)person).Remove("Age");           

我們首先建立一個對象并向其添加兩個屬性。之後,我們将對象強制轉換為接口IDictionary<string, object>。然後,我們調用Remove方法。這将從person對象中删除Age屬性。

對ExpandoObject屬性更改的監聽

ExpandoObject 類還實作了接口INotifyPropertyChanged,每次我們添加、删除或更新對象屬性時,該接口都會觸發一個事件。該事件會通知在PropertyChanged中的訂閱者。

讓我們試看看:

dynamic person = new ExpandoObject();
((INotifyPropertyChanged)person).PropertyChanged += (_, e) =>
{
		_testOutputHelper.WriteLine(#34;Property changed: {e.PropertyName}");
};
person.Name = "Laobai";           

我們首先聲明一個新的動态對象person。然後,我們通過PropertyChanged訂閱了屬性更改的事件。最後,我們向對象person添加一個新屬性Name來觸發事件。調用此方法,我們将得到:

Property changed: Name           

什麼是DynamicObject?

該類允許我們建立自定義動态對象。它幫助我們指定可以對動态對象執行的操作以及如何執行這些操作。

但是,我們隻能在應用程式中繼承該類,因為我們不能直接執行個體化它。繼承類後,我們需要覆寫實作自定義邏輯所需的方法。

我們将在這裡介紹兩個主要方法:

  • TryGetMember()方法
  • TrySetMember()方法

TryGetMember()方法允許我們在運作時自定義動态通路成員值的行為。為了自定義行為,我們必須重寫該方法

public override bool TryGetMember(GetMemberBinder binder, out object result)           

參數GetMemberBinder提供有關正在通路其成員的對象的資訊。result參數是擷取操作的結果。

TrySetMember()方法允許我們自定義設定動态對象成員值的操作:

public override bool TrySetMember(SetMemberBinder binder, object value)           

與此類似,該方法也采用兩個參數。參數SetMemberBinder提供有關正在設定的成員的資訊。參數value是成員将要設定的值。

如果操作成功,這兩種方法都應傳回true,否則,它們應傳回 false。

使用DynamicObject類型

讓我們建立一個繼承DynamicObject的類Person:

public class Person : DynamicObject
{
    private readonly Dictionary<string, object?> _personalInformation;
    public Person()
    {
   		 _personalInformation = new Dictionary<string, object?>();
    }
}           

該類有一個類型Dictionary<string, object?>的和一個構造函數字段。

現在讓我們實作該類的兩個方法:

public class Person : DynamicObject
{
    private readonly Dictionary<string, object?> _personalInformation;
    public Person()
    {
        _personalInformation = new Dictionary<string, object?>();
    }
    public override bool TryGetMember(GetMemberBinder binder, out object? result)
    {
        var key = binder.Name;
        return _personalInformation.TryGetValue(key, out result);
    }
    public override bool TrySetMember(SetMemberBinder binder, object? value)
    {
        var key = binder.Name;
        _personalInformation[key] = value;
        return true;
    }
}           

首先,我們重寫TryGetMember方法以從_personalInformation中擷取屬性的值。然後,TrySetMember方法幫助我們在_personalInformation上設定屬性的值。

為了測試這些方法,我們可以将類Person執行個體化為dynamic:

dynamic person = new Person();
person.Name = "Laobai";
person.Age = 30;
person.Address = "si chou zhilu";           

在本例中,我們将向動态對象添加三個新屬性。

然後我們再向類添加一個新方法:

public void PrintInfo()
{
  Console.WriteLine("Personal Information:");
  foreach (var info in _personalInformation)
  {
  	Console.WriteLine(#34;{info.Key}: {info.Value}");
  }
}           

調用此方法,我們得到:

Personal Information:
Name: Laobai
Age: 30
Address: si chou zhilu           

PrintInfo方法從對象person動态讀取值并将其列印到控制台。

這裡隻是展示了一個非常簡化的用例。但是,這些繼承的方法可以進一步被定制。一個比較好的場景就是從配置檔案中讀取值生成一個動态的配置對象。

在分别檢視了動态類型之後,讓我們繼續檢視它們之間的差異。

ExpandoObject、DynamicObject 和 Dynamic 之間的差異

ExpandoObject 類允許我們在運作時向動态對象執行個體添加成員并動态使用它們。在内部,它實作接口IDictionary<string, object>,使其能夠在鍵值對中存儲屬性和值。

要從中添加或通路ExpandoObject的屬性,我們可以使用點号運算符或将其視為字典。此外,我們不需要顯式定義另一個類或重寫成員即可在我們的應用程式中使用它。

DynamicObject是一個更進階的類,它允許我們自定義動态對象的行為。與類相比,該類更靈活、更強大,因為我們可以使用它來為動态對象的操作建立自定義邏輯。對于我們需要對建立的動态對象進行更多控制的情況,這是一個不錯的選擇。

而dynamic則是一種幫助我們建立動态類型對象的類型。當我們将一個變量聲明為dynamic時,編譯器不會檢查變量的類型。考慮到這一點,我們可以為動态對象配置設定任何類型的值。我們還可以在沒有編譯時檢查的情況下對對象執行操作。它也是在我們使用ExpandoObject和DynamicObject時應該使用的類型。

好了,希望通過此文,我們可以對這三種不同類型有所了解。

最後,Happy Coding Happy Life[憨笑]

繼續閱讀