C#动态类型ExpandoObject,DynamicObject和dynamic傻傻分不清

在 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(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。这意味着每次我们向对象添加新成员时,它们都会存储为键值对。

为了获取ExpandoObject实例的所有值,我们遍历对象:

dynamic country = new ExpandoObject();
country.Name = "China";
country.Population = "1.4 Billion people";
foreach (KeyValuePair keyValuePair in country)
{
		_testOutputHelper.WriteLine($"{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)person).Remove("Age");

我们首先创建一个对象并向其添加两个属性。之后,我们将对象强制转换为接口IDictionary。然后,我们调用Remove方法。这将从person对象中删除Age属性。

对ExpandoObject属性更改的监听

ExpandoObject 类还实现了接口INotifyPropertyChanged,每次我们添加、删除或更新对象属性时,该接口都会触发一个事件。该事件会通知在PropertyChanged中的订阅者。

让我们试看看:

dynamic person = new ExpandoObject();
((INotifyPropertyChanged)person).PropertyChanged += (_, e) =>
{
		_testOutputHelper.WriteLine($"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 _personalInformation;
    public Person()
    {
   		 _personalInformation = new Dictionary();
    }
}

该类有一个类型Dictionary的和一个构造函数字段。

现在让我们实现该类的两个方法:

public class Person : DynamicObject
{
    private readonly Dictionary _personalInformation;
    public Person()
    {
        _personalInformation = new Dictionary();
    }
    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($"{info.Key}: {info.Value}");
  }
}

调用此方法,我们得到:

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

PrintInfo方法从对象person动态读取值并将其打印到控制台。

这里只是展示了一个非常简化的用例。但是,这些继承的方法可以进一步被定制。一个比较好的场景就是从配置文件中读取值生成一个动态的配置对象。

在分别查看了动态类型之后,让我们继续查看它们之间的差异。

ExpandoObject、DynamicObject 和 Dynamic 之间的差异

ExpandoObject 类允许我们在运行时向动态对象实例添加成员并动态使用它们。在内部,它实现接口IDictionary,使其能够在键值对中存储属性和值。

要从中添加或访问ExpandoObject的属性,我们可以使用点号运算符或将其视为字典。此外,我们不需要显式定义另一个类或重写成员即可在我们的应用程序中使用它。

DynamicObject是一个更高级的类,它允许我们自定义动态对象的行为。与类相比,该类更灵活、更强大,因为我们可以使用它来为动态对象的操作创建自定义逻辑。对于我们需要对创建的动态对象进行更多控制的情况,这是一个不错的选择。

而dynamic则是一种帮助我们创建动态类型对象的类型。当我们将一个变量声明为dynamic时,编译器不会检查变量的类型。考虑到这一点,我们可以为动态对象分配任何类型的值。我们还可以在没有编译时检查的情况下对对象执行操作。它也是在我们使用ExpandoObject和DynamicObject时应该使用的类型。

好了,希望通过此文,我们可以对这三种不同类型有所了解。

最后,Happy Coding Happy Life

相关文章

Java 反射 和 Java new 的效率有什么区别,亲测相差100倍

在我们的日常使用中,Java new是用的最多的,但是有些框架往往会使用Java反射来实现灵活性,那么它们之间的效率有什么区别呢?你有没有想过,什么时候应该该用new来创建对象,什么时候该使用反射呢?...

JAVA进阶知识学习-day01 Object类&Date类&System类

一、Object类1.1 java.lang.Object类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。在对象实例化的时候,最终找的父类就是Object。 如果一个类没有...

一文搞懂JAVA 中的引用

介绍JAVA 中有 4 种类型的引用:-强引用-软引用-弱引用-虚引用这些引用仅在垃圾收集器管理它们的方式上有所不同。如果您从未听说过它们,则意味着您只使用过强的。了解其中的区别会对您有所帮助,尤其是...

java反射的核心原理和使用

一、Java 反射的核心概念反射的本质是通过 Class 对象与 JVM 的交互,获取和操作类的字节码数据(如方法、字段等)。以下是反射的核心概念:1.Class 对象:每个 Java 类在 JVM...

Java 反射

Java的反射功能,可以帮我们在程序“运行期间““自由的”创建对象,那为什么强调运行期间呢?那是因为我们创建对象一般是在编译期间就创建好了,例如:我们在代码中new了一个对象,这个时候java源码文件...

java面向对象特性,抽象,封装,继承,多态

1. 抽象(Abstraction)定义: 抽象是指将复杂的事物进行简化,通过隐藏实现细节,只暴露接口或核心功能。它是面向对象编程中的一种机制,允许我们定义“抽象类”和“接口”,从而强制子类实现某些方...