CLR via C# 5.5 dynamic 基元类型断点调试学习

在运行时,C#的 “运行时绑定器” 根据对象的运行时类型分析应采取什么动态操作。绑定器首先检查类型是否实现了 IDynamicMetaObjectProvider 接口。如果是,就调用接口的 GetMetaObject 方法,它返回 DynamicMetaObject 的一个派生类型。该类型能处理对象的所有成员、方法和操作符绑定。 IDynamicMetaObjectProvider 接口和 DynamicMetaObject 基类都在 System.Dynamic 命名空间中定义,都在 System.Core.dll 程序集中。

像 Python 和 Ruby 这样的动态语言,是为它们的类型赋予了从 DynamicMetaObject 派生的类型,以便从其他编程语言(比如 C#)中以恰当的方式访问。类似地,访问 COM 组件时,C#的“运行时绑定器”会使用知道如何与 COM 组件通信的 DynamicMetaObject 派生类型。 COM DynamicMetaObject 派生类型在 System.Dynamic.dll 程序集中定义。

如果在动态表达式中使用的一个对象的类型未实现 IDynamicMetaObjectProvider 接口,C# 编译器会对对象视为用 C# 定义的普通类型的实例,利用反射在对象上执行操作。

dynamic 的一个限制是只能访问对象的实例成员,因为 dynamic 变量必须引用对象。但有时需要动态调用运行时才能确定的一个类型的静态成员。我为此创建了 StaticMemberDynamicWrapper 类,它从 System.Dynamic.DynamicObject 派生。后者实现了 IDynamicMetaObjectProvider 接口。类内部使用了相当多的反射(这个主题将在第 23 章讨论)。以下是我的 StaticMemberDynamicWrapper 类的完整代码。

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;

namespace DynamicDemo
{
    internal sealed class StaticMemberDynamicWrapper : DynamicObject
    {
        private readonly TypeInfo m_type;
        public StaticMemberDynamicWrapper(Type type)
        {
            m_type = type.GetTypeInfo();
        }

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return m_type.DeclaredMembers.Select(mi => mi.Name);
        }

        public override Boolean TryGetMember(GetMemberBinder binder, out object result)
        {
            result = null;
            var field = FindField(binder.Name);
            if (field != null) { result = field.GetValue(null); return true; }

            var prop = FindProperty(binder.Name, true);
            if (prop != null) { result = prop.GetValue(null, null); return true; }
            return false;
        }

        public override Boolean TrySetMember(SetMemberBinder binder, object value)
        {
            var field = FindField(binder.Name);
            if (field != null) { field.SetValue(null, value); return true; }

            var prop = FindProperty(binder.Name, false);
            if (prop != null) { prop.SetValue(null, value, null); return true; }
            return false;
        }

        public override Boolean TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            MethodInfo method = FindMethod(binder.Name, args.Select(c => c.GetType()).ToArray());
            if (method == null) { result = null; return false; }
            result = method.Invoke(null, args);
            return true;
        }

        private MethodInfo FindMethod(String name, Type[] paramTypes)
        {
            return m_type.DeclaredMethods.FirstOrDefault(mi => mi.IsPublic && mi.IsStatic
                                    && mi.Name == name && ParametersMatch(mi.GetParameters(), paramTypes));
        }

        private Boolean ParametersMatch(ParameterInfo[] parameters, Type[] paramTypes)
        {
            if (parameters.Length != paramTypes.Length) return false;
            for (Int32 i = 0; i < parameters.Length; i++)
                if (parameters[i].ParameterType != paramTypes[i]) return false;
            return true;
        }

        private FieldInfo FindField(String name)
        {
            return m_type.DeclaredFields.FirstOrDefault(fi => fi.IsPublic && fi.IsStatic && fi.Name == name);
        }

        private PropertyInfo FindProperty(String name, Boolean get)
        {
            if (get)
                return m_type.DeclaredProperties.FirstOrDefault(
                    pi => pi.Name == name && pi.GetMethod != null &&
                        pi.GetMethod.IsPublic && pi.GetMethod.IsStatic);

            return m_type.DeclaredProperties.FirstOrDefault(
                pi => pi.Name == name && pi.SetMethod != null &&
                    pi.SetMethod.IsPublic && pi.SetMethod.IsStatic);
        }
    }
}

调用

dynamic stringType = new StaticMemberDynamicWrapper(typeof(String));
var r = stringType.Concat("A", "B"); // 动态调用 String 的静态 Concat 方法
Console.WriteLine(r);                // 显示 "AB"

断点调试

  1. 初始化实例,m_type是System.String。

  2. 调用stringType.Concat

  3. 进入TryInvokeMember。binder是Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinderargs"A""B"resultnull

    刚进入FindMethod的时候,发现编译器尝试在电脑上寻找multicastdelegate.cs文件,没找到。

  4. 进入FindMethod方法。name"Concat"paramTypes是长度为2的数组,Value都是System.String
    m_type.DeclaredMethods包含了186个方法,从这186个方法中,寻找满足“是Public”, “是static”, “Method Name是Concat”条件的方法,并且试图通过调用ParametersMatch从中找到参数签名完全一致的方法。

System.String中,MethodName为Concat的方法:

public static String Concat(object arg0);
public static String Concat(IEnumerable<String> values);
public static String Concat(params String[] values);
public static String Concat(params object[] args);
public static String Concat(String str0, String str1);
public static String Concat(object arg0, object arg1);
public static String Concat(String str0, String str1, String str2);
public static String Concat(object arg0, object arg1, object arg2);
public static String Concat(String str0, String str1, String str2, String str3);
public static String Concat(object arg0, object arg1, object arg2, object arg3);
public static String Concat<T>(IEnumerable<T> values);

刚进入m_type.DeclaredMethods.FirstOrDefault的时候,发现编译器尝试在电脑上寻找multicastdelegate.cs文件,没找到。

  1. ParametersMatch方法会被调用若干次。parameters接收的是“试图寻找的方法,接收的参数数量和类型”,paramTypes接收的是“外部调用者,传入的参数数量和类型”(在此处是长度为2的数组,Value都是System.String)。
  • 第一次进入,Concat接受的参数是一个Object,Length不匹配,返回False。
  • 第二次进入,Concat接受的参数是两个Object,Length匹配,但是参数类型不匹配,返回False。
  • 第三次进入,Concat接受的参数是三个Object,Length不匹配,返回False。
  • 第四次进入,Concat接受的参数是四个Object,Length不匹配,返回False。
  • 第五次进入,Concat接受的参数是一个Object[],Length不匹配,返回False。
  • 第六次进入,Concat接受的参数是一个IEnumerable<System.String>,Length不匹配,返回False。
  • 第七次进入,Concat接受的参数是两个System.String,Length匹配,参数类型匹配两次,都通过。返回True。
  1. FindMethod返回System.String Concat(System.String, System.String)

  2. 运行method.Invoke(null, args);,断点进入MethodBase.Invoke(object obj, object[] parameters)。调用成功后,result被赋值为"AB",返回True

试图调用method.Invoke时,尝试寻找MethodInfo.cs,没找到

MethodBase.Invoke内部断点的时候,尝试寻找rttype.cs,没找到

  1. 调用者的r被赋值"AB",结束。
posted @ 2021-12-27 15:16  Destiny130  阅读(79)  评论(0)    收藏  举报