动态对象

动态对象是一种特殊类型的对象,其成员(属性和方法)的解析是在运行时而不是编译时确定的。这种类型的对象允许你在编写代码时不需要提前知道对象的成员,而是可以在运行时根据需要动态添加和调用成员。其相关类型主要是在System.Dynamic命名空间中,虽然其下有很多的类,但是最常用的就是ExpandoObject类和DynamicObject类。

ExpandoObject

介绍

ExpandoObject是C#中实现的一种动态类型,它允许在运行时动态地添加和删除成员。与静态类型不同,ExpandoObject的成员在编译时不需要提前定义,而是可以在运行时根据需要进行扩展。

  • ExpandoObject显式实现了IDictionary<string, object?>接口,可以通过转换成IDictionary<string, object?>类型来进行成员的操作。
  • ExpandoObject显式实现了INotifyPropertyChanged接口,可以接收成员更改的通知。
  • ExpandoObject是线程安全的,在添加/修改/删除成员时有互斥锁。
  • ExpandoObject的实现其实是数组,将key和value分别放在ExpandoClassExpandoData两个类中,这两个类的核心都是数组。

示例

下面是一个简单的示例,演示如何使用ExpandoObject来创建使用一个动态类型的对象。

//必须要使用dynamic关键字来接收
dynamic obj = new ExpandoObject();

//添加/修改成员
obj.Name = "名称";
obj.age = 20;
//不能直接将lambda表达式转换为dynamic,因为他不是委托类型
obj.Read = (Action)(() => Debug.WriteLine("test..."));
obj.Read();

//移除成员,ExpandoObject自身没有移除的方法
//需要转换成IDictionary<string, object?>才能调用其Remove方法移除
((IDictionary<string, object?>)obj).Remove("Name");

//订阅成员更改通知
((INotifyPropertyChanged)obj).PropertyChanged += new PropertyChangedEventHandler((sender, e) =>
{
    Console.WriteLine($"{e.PropertyName}变更了。");
});

DynamicObject

介绍

DynamicObject是一个特殊的基类,它允许您创建动态对象,并在运行时动态地处理成员访问、方法调用和运算符操作。他的存在使得C#编程更加灵活,特别是在与动态语言进行交互或者需要在运行时生成对象成员的情况下。

  • DynamicObject的唯一一个构造函数是protected的,所以需要子类继承他并实现其动态行为。
  • DynamicObject的核心可重写方法如下:
    1. TryGetMember:用于在访问动态对象的成员时进行处理。
    2. TrySetMember:用于在设置动态对象的成员时进行处理。
    3. TryInvokeMember:用于在调用动态对象的方法时进行处理。
    4. TryInvoke:用于在调用动态对象本身时进行处理。
    5. TryConvert:用于在将动态对象转换为其他类型时进行处理。
    6. TryBinaryOperation:用于在进行二元操作时进行处理。
    7. TryUnaryOperation:用于在进行一元操作时进行处理。

示例

下面是一个简单的示例,演示如何继承DynamicObject后来创建使用一个动态类型的对象。

/// <summary>
/// 自定义动态类型
/// </summary>
public class CustomDynamicObject : DynamicObject
{
    private readonly Dictionary<string, object?> caches = [];

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

    /// <summary>
    /// 在调用其成员的时候都会先调用此方法以获取动态操作的元数据
    /// </summary>
    /// <param name="parameter"></param>
    /// <returns></returns>
    public override DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return base.GetMetaObject(parameter);
    }

    /// <summary>
    /// 提供二元操作的实现,加减乘除、左移右移等。
    /// </summary>
    /// <param name="binder"></param>
    /// <param name="arg"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result)
    {
        //看具体的实现
        //比如动态对象+1,或者两个动态对象相加,你想得到的是什么?具体逻辑具体处理后再给result赋值即可
        //通过BinaryOperationBinder.Operation来判断二元操作是什么
        switch (binder.Operation)
        {
            case ExpressionType.Add: //相加
                //具体实现
                break;
            //......
        }
        result = new CustomDynamicObject();
        return true;
    }

    /// <summary>
    /// 提供转换成其他类型的实现(他的基类除外,如DynamicObject和Object)
    /// !!!注意:使用as来转换是不会触发的
    /// </summary>
    /// <param name="binder"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public override bool TryConvert(ConvertBinder binder, out object? result)
    {
        //看具体的实现
        //比如转换成int类型的,通过ConvertBinder.ReturnType来判断要转换的类型,处理后再给result赋值即可
        if (binder.ReturnType == typeof(int))
        {
            //具体实现
        }
        result = this;
        return true;
    }

    /// <summary>
    /// 【不用管】提供创建新实例的操作的实现
    /// !!!这个方法不适合在c#或Visual Basic中使用,所以无论是new还是Activator等都没法触发这个方法而是直接走的构造函数了
    /// </summary>
    /// <param name="binder"></param>
    /// <param name="args"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public override bool TryCreateInstance(CreateInstanceBinder binder, object?[]? args, [NotNullWhen(true)] out object? result)
    {
        return base.TryCreateInstance(binder, args, out result);
    }

    /// <summary>
    /// 【不用管】提供按索引删除对象的操作的实现
    /// !!!这个方法不适合在c#或Visual Basic中使用
    /// </summary>
    /// <param name="binder"></param>
    /// <param name="indexes"></param>
    /// <returns></returns>
    public override bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes)
    {
        return base.TryDeleteIndex(binder, indexes);
    }

    /// <summary>
    /// 【不用管】提供删除对象成员的操作的实现
    /// !!!这个方法不适合在c#或Visual Basic中使用
    /// </summary>
    /// <param name="binder"></param>
    /// <returns></returns>
    public override bool TryDeleteMember(DeleteMemberBinder binder)
    {
        return base.TryDeleteMember(binder);
    }

    /// <summary>
    /// 提供按索引获取值的操作的实现,索引支持多个,如obj[0]、obj["name"]、obj[0,1,2,3]等等都可以的,看实现
    /// </summary>
    /// <param name="binder"></param>
    /// <param name="indexes"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object? result)
    {
        //看具体的实现
        if (indexes.Length == 1 && indexes[0] != null)
        {
            result = caches.GetValueOrDefault(indexes[0].ToString()!);
        }
        else
        {
            result = null;
        }
        return true;
    }

    /// <summary>
    /// 提供获取成员值的操作的实现
    /// </summary>
    /// <param name="binder"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public override bool TryGetMember(GetMemberBinder binder, out object? result)
    {
        return caches.TryGetValue(binder.Name, out result);
    }

    /// <summary>
    /// 提供调用对象的操作的实现,就是把这个动态对象当做方法来调用,操作如obj()、obj("name")等
    /// </summary>
    /// <param name="binder"></param>
    /// <param name="args"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public override bool TryInvoke(InvokeBinder binder, object?[]? args, out object? result)
    {
        //看具体的实现
        if (args?.Length == 1 && args[0] != null)
        {
            return caches.TryGetValue(args[0]!.ToString()!, out result);
        }
        else
        {
            result = null;
        }
        return true;
    }

    /// <summary>
    /// 提供调用成员的操作的实现,如obj.GetName()等等
    /// </summary>
    /// <param name="binder"></param>
    /// <param name="args"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args, out object? result)
    {
        //看具体的实现
        result = false;
        if (binder.Name.Equals("remove", StringComparison.CurrentCultureIgnoreCase) && args?.Length == 1 && args[0] != null)
        {
            result = caches.Remove(args[0]!.ToString()!);
        }
        return (bool)result;
    }

    /// <summary>
    /// 提供按索引设置值的操作的实现,索引支持多个,如obj[0]、obj["name"]、obj[0,1,2,3]等等都可以的,看实现
    /// </summary>
    /// <param name="binder"></param>
    /// <param name="indexes"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object? value)
    {
        //看具体的实现
        if (indexes.Length == 1 && indexes[0] != null)
        {
            caches.Add(indexes[0].ToString()!, value);
        }
        return true;
    }

    /// <summary>
    /// 提供设置成员值的操作的实现
    /// </summary>
    /// <param name="binder"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public override bool TrySetMember(SetMemberBinder binder, object? value)
    {
        return caches.TryAdd(binder.Name, value);
    }

    /// <summary>
    /// 提供一元操作的实现,如取反、自增或自减等
    /// </summary>
    /// <param name="binder"></param>
    /// <param name="result"></param>
    /// <returns></returns>
    public override bool TryUnaryOperation(UnaryOperationBinder binder, out object? result)
    {
        //看具体实现
        result = 1;
        return true;
    }
}

使用方式如下:

dynamic obj = new CustomDynamicObject();

obj = obj + 1; //会触发【TryBinaryOperation】方法

ExpandoObject obj2 = (ExpandoObject)obj; //会触发【TryConvert】方法

obj["name"] = "张三"; //会触发【TrySetIndex】方法

string name = obj["name"]; //会触发【TryGetIndex】方法

obj.name = "李四"; //会触发【TrySetMember】方法

string name1 = obj.name; //会触发【TryGetMember】方法

string name2 = obj("name"); //会触发【TryInvoke】方法

obj.Remove("name"); //会触发【TryInvokeMember】方法

obj++; //会触发【TryUnaryOperation】方法