在利用 Type 类进行反射时,经常用到 GetMethod 和 GetProperty 反射方法与属性,或者使用 InvokeMember 直接调用类型成员。这些方法都具有一个 System.Reflection.Binder 类型的 binder 参数,而这个参数一般都是设置为 null 的,很少使用。

事实上,这个 binder 参数是很强大的,它可以几乎完全控制反射的工作方式(这里用几乎,是因为它受到了 RuntimeType 实现时的一些限制),只不过默认情况下使用的 System.DefaultBinder 类已经足够的使用了,因此不用太过于在意这个参数。

下面将会以我实现的 PowerBinder 类作为例子,解释 Binder 类到底是做什么的,以及如何实现自己的 Binder 类。PowerBinder 的实现与 DefaultBinder 的逻辑是基本相同的,区别在于添加了对泛型方法和强制类型转换的支持,同时进行了部分改进,下面给出一个与 DefaultBinder 对比的例子:

class TestClass
{
	public static void TestMethod(int value) { }
	public static void TestMethod2<T>(T value) { }
}
Type type = typeof(TestClass);
Console.WriteLine(type.GetMethod("TestMethod", new Type[] { typeof(long) }));
Console.WriteLine(type.GetMethod("TestMethod", BindingFlags.Static | BindingFlags.Public, PowerBinder.CastBinder,
	new Type[] { typeof(long) }, null));
Console.WriteLine(type.GetMethod("TestMethod2", new Type[] { typeof(string) }));
Console.WriteLine(type.GetMethod("TestMethod2", BindingFlags.Static | BindingFlags.Public, PowerBinder.DefaultBinder,
	new Type[] { typeof(string) }, null));

这个例子是分别用 DefaultBinder 和 PowerBinder 反射获取 TestClass 类的方法,得到的结果如下所示:

null
Void TestMethod(Int32)
null
Void TestMethod2[String](System.String)

可以看到,有了泛型方法和强制类型转换的支持,在反射调用方法时会更加灵活方便,而且自定义 Binder 类的好处是很容易重用,而且能够使用 .Net 提供的相关接口。

一、Binder 类介绍

首先来看 Binder 类是如何控制反射的工作方式的。它在 MSDN 中的解释是“从候选者列表中选择一个成员,并执行实参类型到形参类型的类型转换。”,也就是说在执行反射时,会由 Type 类选出一组可能的 MethodBase、PropertyInfo 或 FieldInfo,然后由 Binder 类来决定到底要使用哪个方法、属性或字段,或者干脆哪个都不选;而且实参到形参的类型转换(会在 Invoke 时使用)也是由 Binder 类来控制的,所以说它可以几乎完全控制反射的工作方式——唯一的不足就是候选者列表是由 Type 类提供的。

这里列出了 Binder 类需要重写的方法和简要的说明,每个方法的参数的具体解释可以参考 MSDN

  1. FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, Object value, CultureInfo culture) 方法:
    当利用 Type.InvokeMember 方法访问字段时,先由 InvokeMember 方法选出与 name 和 bindingFlags 匹配的字段,然后由 BindToField 选择与 value 最匹配的字段。
  2. MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref Object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] names, out Object state) 方法:
    当利用 Type.InvokeMember 方法调用方法或访问属性时,先由 InvokeMember 方法选出与 name 和 bindingFlags 匹配的方法(属性则使用相应的 SetMethod 或 GetMethod),然后由 BindToMethod 选择与 args 最匹配的方法。这个方法非常复杂,由于 InvokeMember 方法允许通过参数名称指定参数,因此参数的顺序和个数与方法的形参可能并不匹配,需要由 BindToMethod 方法将参数数组调整为正确的顺序,并且要求 ReorderArgumentArray 方法配合附加的 out state 参数,将被改变的参数数组顺序还原为被传入时的顺序。
  3. Object ChangeType(Object value, Type type, CultureInfo culture) 方法:
    这个方法用于在利用反射设置值时(例如 FieldInfo.SetValue 和 MethodBase.Invoke),对类型进行转换。MSDN 建议只进行扩宽强制,这样不会丢失数据,但也可以实现自己的逻辑。
  4. void ReorderArgumentArray(ref Object[] args, Object state) 方法:
    这个方法是与 BindToMethod 成对使用的,根据 BindToMethod 的 out state 参数,还原 args 的参数顺序。两个方法的实现也必须相对应。
  5. MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers) 方法:
    当利用 Type.GetMethod 反射方法时,先由 GetMethod 方法选出与 name、bindingFlags、callConvention 和参数数量匹配的方法,然后由 SelectMethod 选择与 types 最匹配的方法。
  6. PropertyInfo SelectProperty(BindingFlags bindingAttr, PropertyInfo[] match, Type returnType, Type[] indexes, ParameterModifier[] modifiers) 方法:
    当利用 Type.GetProperty 反射属性时,先由 GetProperty 方法选出与 name、bindingFlags 和参数数量匹配的属性,然后由 SelectProperty 选择与 returnType 和 indexes 最匹配的属性。

二、支持泛型方法和强制类型转换的 PowerBinder 类

接下来就是详细解释 PowerBinder 是如何实现每个方法的。

2.1 实现 BindToField 方法

先再次列出方法签名,以方便参考: FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, Object value, CultureInfo culture)。

这个方法其实很少被使用,仅当父类和子类定义了同名字段时才可能使用(否则根本不能定义同名的字段)。下面是 BindToField 方法的实现流程图,在这个流程图中也显示出了 RuntimeType 类为我们做的一些工作。

C# 使用 Binder 类自定义反射 update 2013.1.26

这个方法的实现还是很简单的,不过有些地方需要详细解释一下。

  • 如果设置了 GetField 标志,RuntimeType 会将 value 设置为一个特殊的值 Empty.Value(这是一个内部类),所以这时候 value 是完全不可用的。
  • 如果设置了 SetField 标志而 value == null,这时只要求 FieldType 是引用类型即可,即任何可以接受 null 值的类型。
  • 如果按照 value 的类型筛选字段仍然得到多个可选字段,可以尝试从匹配的字段中找到最匹配的那个。例如有两个字段,其类型分别是 int 和 long,与 short 类型的值更匹配的显然是 int 而不是 long。这一匹配方式在后面也会多次用到。

至于如何选择定义在子类中的字段,可以简单的按照 FieldInfo 被定义的深度来选择,深度比较深的就意味着是在子类中定义的。

.Net 4.0 中的 RuntimeType 类的实现有个小小的问题,现在假设类型 C 具有一个 string[] 类型的字段 F,当想通过 InvokeMemver 将 F[1] 设置为 "b" 时(一种很少见的用法,可能很多人都不知道),可以使用下面的代码(更多信息请参见 MSDN):

typeof(C).InvokeMember("F", BindingFlags.SetField, null, c, new Object[] {1, "b"}, null, null, null);

但是,此时 BindToField 方法的 value 参数得到的不是要设置的值 "b",也不是 string[] 类型的值,而是那个索引 1,这就导致 BindToField 是不可能通过类型选择合适的字段的(甚至可能选择错误)。下面就是一个例子:

class TestClass
{
	public string[] TestField = new string[] { "XXX" };
}
class TestSubClass
{
	public new int TestField;
}

当调用

typeof(TestSubClass).InvokeMember("TestField", BindingFlags.SetField, null, new TestSubClass(), new object[] { 0, "XXX2" }, null, null, null);

时,就不能正确的反射到 TestClass.TestField,会抛出 ArgumentException。不过还好,这个问题几乎不可能遇到,即使真的出现这种问题,先获取字段对应的数组,再获取或设置数组指定索引的值就可以完美解决了。

下面是实现的代码:

public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, object value,
	CultureInfo culture)
{
	int idx = 0;
	Type valueType = null;
	if (value != null) { valueType = value.GetType(); }
	bool setField = (bindingAttr & BindingFlags.SetField) != 0;
	if (setField)
	{
		// 在设置 SetField 标志时,根据 value 的类型进行选择。
		for (int i = 0; i < match.Length; i++)
		{
			if (CanChangeType(match[i].FieldType, valueType))
			{
				match[idx++] = match[i];
			}
		}
		if (idx == 0)
		{
			// 没有可匹配的字段。
			return null;
		}
		else if (idx > 1 && valueType != null)
		{
			// 多个可匹配字段,尝试寻找类型匹配的最好的字段。
			int len = idx;
			idx = 1;
			for (int i = 1; i < len; i++)
			{
				// 尝试进一步匹配字段类型。
				int cmp = FindMostSpecificType(match[0].FieldType, match[i].FieldType, valueType);
				if (cmp == 0)
				{
					match[idx++] = match[i];
				}
				else if (cmp == 2)
				{
					match[0] = match[i];
					idx = 1;
				}
			}
		}
	}
	else
	{
		idx = match.Length;
	}
	// 多个可匹配字段,寻找定义深度最深的字段。
	int min = 0;
	bool ambig = false;
	for (int i = 1; i < idx; i++)
	{
		// 比较定义的层级深度。
		int cmp = CompareHierarchyDepth(match[min], match[i]);
		if (cmp == 0)
		{
			ambig = true;
		}
		else if (cmp == 2)
		{
			min = i;
			ambig = false;
		}
	}
	if (ambig)
	{
		throw ExceptionHelper.AmbiguousMatchField();
	}
	return match[min];
}

其中用到的 FindMostSpecificType(Type type1, Type type2, Type type) 方法,是在两个类型 type1 和 type2 中,选择与 type 最接近的类型。例如在类型 short 和 int 中,与 long 最接近的显然是 int 类型,而与 sbyte 最接近的则是 short 类型。 具体的做法,就是判断 type1 和 type2 中哪个可以从 type 类型隐式转换而来,没有数据的丢失显然是更好的;如果都可以从 type 类型隐式转换而来,那么就选择 type1 和 type2 中更窄的那个(更接近 type);如果都不可以从 type 类型隐式转换,那么就选择更宽的那个,以减少数据丢失。

2.2 实现 BindToMethod 方法

方法的签名为 MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref Object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] names, out Object state)。

这个方法是重写 Binder 类时最复杂的方法,它需要处理的情况非常多,包括参数名映射,可选参数、params 参数和泛型方法。我将 BindToMethod 方法的实现分成了下面的五个步骤。为了简便起见,这里与 System.DefaultBinder 一样不对 modifiers 参数进行处理(它一般都是用于 COM 组件的)。下面就是 BindToMethod 方法的实现流程图,虽然看起来不是很复杂,但其中的每一个步骤都需要做很多的工作。

C# 使用 Binder 类自定义反射 update 2013.1.26

2.2.1 处理 names 参数

names 参数允许参数不按顺序传入,所以首先要对 names 参数进行检查,要求 names 中不能有同名参数。在 DefaultBinder 并没有做这个检查,所以当存在同名参数时,会出现诡异的 IndexOutOfRangeException。

接下来根据 names 参数调整参数的位置,就是在方法的参数列表中寻找与 names 中的名称相同的参数,直到所有参数名称都被匹配(如果有未被匹配的参数名称,那么认为这个方法就不是想要的),在这里定义映射 map 来保存参数与 names 的匹配关系:如果 names[i] == params[j].Name,则 map[j] = i。

以方法

void TestMethod(int value1 = 11, int value2 = 22, int value3 = 33)

举例来说:

  1. 若 names = null, 则 map = {0, 1, 2},表示参数都是按顺序传递的。
  2. 若 names = {"value2", "value1", "value3"}, 则 map = {1, 0, 2}。
  3. 若 names = {"value3"}, 则 map = {1, 2, 0},即 names 的个数小于参数的个数时,剩余的参数会按顺序传递。

具体的实现为:

private static bool CreateParamOrder(int[] paramOrder, ParameterInfo[] parameters, string[] names)
{
	if (names.Length > parameters.Length)
	{
		return false;
	}
	for (int i = 0; i < paramOrder.Length; i++)
	{
		paramOrder[i] = -1;
	}
	// 找到与参数名称对应的参数索引,names.Length <= parameters.Length。
	for (int i = 0; i < names.Length; i++)
	{
		int j;
		for (j = 0; j < parameters.Length; j++)
		{
			if (string.Equals(parameters[j].Name, names[i], StringComparison.Ordinal))
			{
				paramOrder[j] = i;
				break;
			}
		}
		// 未找到的参数名称,匹配失败。
		if (j == parameters.Length)
		{
			return false;
		}
	}
	// 依次填充剩余的 args 的参数顺序。
	int idx = names.Length;
	for (int i = 0; i < paramOrder.Length; i++)
	{
		if (paramOrder[i] == -1)
		{
			paramOrder[i] = idx++;
		}
	}
	return true;
}

2.2.2 处理泛型方法

接下来就是对泛型方法的支持了,如果函数签名是 TestMethod<T>,这里需要将开放的泛型参数 T 替换为合适的类型,以得到相应的封闭泛型方法(例如 TestMethod<int>)。如果泛型参数 T 只对应一个参数 p,把 p 的类型作为 T 的类型即可。如果对应多个参数 p1, p2 ... pn,则要选择 pi 的类型,使得其他参数的类型都可以隐式转换为 pi 的类型。如果没有对应任何参数,那么显然是不能推导出类型实参的,直接返回。

如果泛型参数 T 对应着两个参数 pi 和 pj,其中 p1, p2 ... pn 都可以隐式转换为 pi 和 pj 的类型,那么泛型参数 T 的类型既可以选择 pi 的类型,也可以选择 pj 的类型,但到底使用哪个,程序是不能确定的,因此要求类型实参的推导必须是唯一的。

需要注意的是,这里使用的都是隐式类型转换,而不是显式类型转换,这是由于显示类型转换很容易导致找不到唯一的类型实参的推导,因此只遵循通常的原则。

2.2.3 根据参数类型筛选

然后就是根据参数类型进行过滤,即依次比较 params[i].ParameterType 是否可以从 args[map[i]] 的类型转换而来。而具体的比较又要分为三种情况分别讨论,

  1. params.Length > args.Length 这种情况意味着部分参数没有给出,因此要求没有给出值的参数(map[i] >= args.Length)具有默认值(DefaultValue != DBNull.Value),而且同时指定了 BindingFlags.OptionalParamBinding 标志。特别的,若最后一个参数是 params 参数,是没有默认值的,需要特殊处理一下。
    System.DefaultBinder 类在这里有个问题,就是要求默认参数总是在参数列表的末尾,即使是使用 names 更改参数顺序也不允许默认参数出现在参数列表的中间。拿之前定义的 TestMethod 举例来说,若传入 name = {"value2"}, args = {1},System.DefaultBinder 会抛出 IndexOutOfRangeException。在我实现的 PowerBinder 中,则允许默认参数出现在任意位置。
  2. params.Length < args.Length 这种情况意味着给出的参数多于方法的参数,即方法必须包含 params 参数,这时就需要检查 args 的额外参数值能否强制类型转换到 params 参数的基础类型。
  3. params.Length == args.Length 这就是最常见的情况,但是要根据最后一个参数是否是 params 参数进行额外的判断,因为参数值既可以是数组的一个元素,也可以是只包含一个元素的数组。

具体的实现代码如下:

private bool CheckMethodParameters(MatchInfo method, Type[] types, bool optionalParamBinding)
{
	if (method == null)
	{
		return false;
	}
	int len = method.Parameters.Length;
	if (len == 0)
	{
		// 判断包含变量参数的方法。
		return types.Length == 0 || (method.Method.CallingConvention & CallingConventions.VarArgs) != 0;
	}
	else if (len > types.Length)
	{
		// 方法形参过多,要求指定可选参数绑定。
		if (!optionalParamBinding)
		{
			return false;
		}
		// 参数必须有默认值,最后一个参数可能是 params 参数,因此稍后进行检查。
		int i = 0;
		for (; i < len - 1; i++)
		{
			if (method.ParamOrder[i] >= types.Length && method.Parameters[i].DefaultValue == DBNull.Value)
			{
				return false;
			}
		}
		// 检查最后一个参数是否有默认值,或者是 params 参数。
		if (method.ParamOrder[i] >= types.Length && method.Parameters[i].DefaultValue == DBNull.Value)
		{
			if ((method.ParamArrayType = GetParamArrayType(method.Parameters[i])) == null)
			{
				return false;
			}
		}
		// 检查其它参数是否可以进行类型转换。
		return CheckParameters(method, types, types.Length);
	}
	else if (len < types.Length)
	{
		len--;
		// 方法形参过多,要求具有 params 参数。
		if ((method.ParamArrayType = GetParamArrayType(method.Parameters[len])) == null)
		{
			return false;
		}
		// 检查参数是否可以进行类型转换。
		for (int i = len; i < types.Length; i++)
		{
			if (!CanChangeType(method.ParamArrayType, types, method.ParamOrder[i]))
			{
				return false;
			}
		}
		return CheckParameters(method, types, len);
	}
	else
	{
		// 参数数量相等。
		len--;
		if ((method.ParamArrayType = GetParamArrayType(method.Parameters[len])) != null)
		{
			// 判断是否需要展开 params 参数。
			if (!CanChangeType(method.ParamArrayType, types, method.ParamOrder[len]))
			{
				// 不需要展开 params 参数。
				method.ParamArrayType = null;
			}
			else
			{
				// 需要展开 params 参数。
				if (!CanChangeType(method.ParamArrayType, types, method.ParamOrder[len]))
				{
					return false;
				}
			}
		}
		else
		{
			// 没有 params 参数。
			len++;
		}
		return CheckParameters(method, types, len);
	}
}

2.2.4 进一步匹配方法

通过上面的参数类型匹配,可能找到多个合适的方法,那么现在就需要在这些方法中,找到最合适的那个,其基本思想就是看哪个函数的签名与 args 的类型最为接近,实现起来跟 FindMostSpecificType 接近,只不过需要同时考虑多个类型。

如果参数类型同样接近,那么类型特化的方法总是优于泛型方法,子类定义的方法总是优于父类定义的方法(通过比较层级深度)。

2.2.5 保存与调整参数顺序

由于参数的顺序需要根据 names 或默认参数进行调整,所以需要更改参数数组以匹配方法的签名,在这之前则需要保存旧的参数顺序,以用于之后的 ReorderArgumentArray 方法还原参数数组。这里为了简便起见,直接将参数数组复制一份(浅复制)保存,这样还原的时候直接替换就可以了。

对参数数组的调整首先要根据 names 调整顺序,接下来对默认参数和 params 参数进行处理,方式则类似于 2.2.3 中匹配参数类型,只不过是需要将多的参数包装为数组,或者将缺少的参数使用默认值补齐。实现的代码如下所示:

private static void UpdateArgs(MatchInfo match, ref object[] args, bool orderChanged, out object state)
{
	// 最简单的参数完全不需要调整的情况。
	if (match.Parameters.Length == 0 ||
		(match.Parameters.Length == args.Length && match.ParamArrayType == null && !orderChanged))
	{
		state = null;
		return;
	}
	// 保存旧的参数状态。
	object[] oldArgs = args;
	state = oldArgs;
	args = new object[match.Parameters.Length];
	int end = match.Parameters.Length - 1;
	// 根据名称调整参数顺序,同时使用默认值填充剩余参数。
	for (int i = match.ParamArrayType == null ? end : end - 1; i >= 0; i--)
	{
		if (match.ParamOrder[i] < oldArgs.Length)
		{
			args[i] = oldArgs[match.ParamOrder[i]];
		}
		else
		{
			args[i] = match.Parameters[i].DefaultValue;
		}
	}
	if (match.Parameters.Length >= oldArgs.Length)
	{
		// 对 params 参数进行判断。
		if (match.ParamArrayType != null)
		{
			Array paramsArray = null;
			if (match.ParamOrder[end] < oldArgs.Length)
			{
				// 最后一个参数是只有一个元素的数组。
				paramsArray = Array.CreateInstance(match.ParamArrayType, 1);
				paramsArray.SetValue(oldArgs[match.ParamOrder[end]], 0);
			}
			else
			{
				// 最后一个参数是空数组。
				paramsArray = Array.CreateInstance(match.ParamArrayType, 0);
			}
			args[end] = paramsArray;
		}
	}
	else
	{
		// 参数过多,将多余的参数包装为一个数组。
		if ((match.Method.CallingConvention & CallingConventions.VarArgs) == 0)
		{
			Array paramsArray = Array.CreateInstance(match.ParamArrayType, oldArgs.Length - end);
			for (int i = 0; i < paramsArray.Length; i++)
			{
				paramsArray.SetValue(oldArgs[match.ParamOrder[i + end]], i);
			}
			args[end] = paramsArray;
		}
	}
}

2013.1.26 更新:在后来对 PowerBinder 的一些测试过程中,发现了现有算法对一些参数顺序被更改的情况不能正确处理,例如下面的类:

class TestClass
{
	public static string TestMethod(int ivalue, string svalue);
	public static string TestMethod(string svalue, int ivalue, string evalue = "str");
}

两个方法重载的区别就是参数的顺序,和第二个方法包含一个可选参数。根据通常的想法,下面的代码:

typeof(TestClass).InvokeMember("TestMethod", 
	BindingFlags.Static | BindingFlags.Public | BindingFlags.OptionalParamBinding | BindingFlags.InvokeMethod, 
	PowerBinder.DefaultBinder, null, new object[] { "ff", 10 }, null, null, new string[] { "svalue", "ivalue" });

调用的方法应该是第一个,即 TestMethod(int ivalue, string svalue),因为它可以与参数完全匹配。但事实上,由 PowerBinder 选择出来的反而是第二个方法,分析代码发现,是在 FindMostSpecific 方法中对参数类型进行进一步匹配时,使用的是这样的代码:

FindMostSpecificType(match1.Parameters[i].ParameterType, match2.Parameters[i].ParameterType, types[i])

其中并没有没有考虑到参数顺序的影响,所以根据参数顺序的不同,选择的结果也是不同的,某些情况下就会导致错误。这里要修改也比较容易,因为已经有 ParamOrder 记录参数顺序的映射,再定义一个逆映射(即若 map[i] = j,则 revMap[j] = i),这样才能够保证比较的的确是对应的参数,如下面的代码所示:

Type type1, type2;
// 得到 types[i] 实际对应的方法参数。
int idx = match1.ParamOrderRev[i];
if (match1.ParamArrayType != null && idx >= p1Len)
{
	type1 = match1.ParamArrayType;
}
else
{
	type1 = match1.Parameters[idx].ParameterType;
}
idx = match2.ParamOrderRev[i];
if (match2.ParamArrayType != null && idx >= p2Len)
{
	type2 = match2.ParamArrayType;
}
else
{
	type2 = match2.Parameters[idx].ParameterType;
}

这样的处理在 System.DefaultBinder 中是存在的,但是它并没有使用逆映射,而是使用原先的 ParamOrder,不知道这是一个 Bug,还是有什么我忽略掉的地方。

除此之外,在完成参数比较之后,如果所有参数都不能比较出优劣(注意,不能是部分参数第一个方法好,部分参数第二个方法好),可以比较两个方法的参数数量,数量较少的方法一定更合适。

而在对泛型方法的支持上,现在只能支持直接使用泛型参数 T 作为参数类型,对于 T[],IList<T> 之类的情况是不能处理的。要想支持这些情况需要做很多工作,如果确定要做的话,我会另写一篇单独说明这个问题。

2.3 实现 ChangeType 方法

这个方法用于实现类型转换,它的逻辑需要和 BindToField 和 BindToMethod 相匹配,即如果 BindToXXX 方法只选择可以隐式类型转换的类型,那么 ChangeType 同样只需要处理隐式类型转换;如果 BindToXXX 方法对现实类型转换提供支持,ChangeType 也必须提供同样的支持。

System.DefaultBinder 只支持内置的隐式类型转换,所以直接抛出 NotSupportedException 就完成工作了。而我的 PowerBinder 支持完整的隐式类型转换和显式类型转换(包括对 Nullable<T>,枚举和自定义类型转换)的支持,因此实现起来会复杂很多,但其原理已经在之前的 C# 判断类型间能否隐式或强制类型转换中阐述了,所以这里就不再详细说明,可以自行看源代码:

public override object ChangeType(object value, Type type, CultureInfo culture)
{
	if (allowCast)
	{
		return ConvertExt.ChangeType(value, type, culture);
	}
	// 隐式类型转换。
	if (type.IsByRef)
	{
		type = type.GetElementType();
	}
	// 总是可以转换为 Object。
	if (type.TypeHandle.Equals(typeof(object).TypeHandle))
	{
		return value;
	}
	// 对 Nullable<T> 的支持。
	bool nullalbe = TypeExt.NullableAssignableFrom(ref type);
	if (value == null)
	{
		if (!nullalbe && type.IsValueType)
		{
			throw ExceptionHelper.CannotCastNullToValueType();
		}
		return null;
	}
	if (type.IsInstanceOfType(value))
	{
		return value;
	}
	// 检测用户定义类型转换。
	RuntimeTypeHandle conversionHandle = type.TypeHandle;
	RuntimeTypeHandle valueTypeHandle = value.GetType().TypeHandle;
	ConversionMethod method;
	if (ConversionCache.GetTypeOperators(valueTypeHandle).TryGetValue(conversionHandle, out method) &&
		(method.ConversionType & ConversionType.ImplicitTo) == ConversionType.ImplicitTo)
	{
		return MethodInfo.GetMethodFromHandle(method.ToMethod).Invoke(null, new object[] { value });
	}
	if (ConversionCache.GetTypeOperators(conversionHandle).TryGetValue(valueTypeHandle, out method) &&
		(method.ConversionType & ConversionType.ImplicitFrom) == ConversionType.ImplicitFrom)
	{
		return MethodInfo.GetMethodFromHandle(method.FromMethod).Invoke(null, new object[] { value });
	}
	return value;
}

2.4 实现 ReorderArgumentArray 方法

这个方法是最简单的,实现的方式也在 2.2 节实现 BindToMethod 方法中说明了,这里直接略过。

2.5 实现 SelectMethod 方法

这个方法也没有必要详细说明,因为它的实现已经完整包含在 BindToMethod 中了,只不过不必考虑 names 参数而已,可以认为是“2.2.2 处理泛型方法”,“2.2.3 根据参数类型筛选”和“2.2.4 进一步匹配方法”这三节的内容的组合。

不过,这里还是有些细节问题。在 DefaultBinder 中,SelectMethod 是不会考虑可选参数、params 参数和泛型方法的,我的 PowerBinder 决定要加入对他们的支持。但是在 RuntimeType 中,会对方法的参数数量进行初步筛选,如果没有设置 BindingFlags.InvokeMember、BindingFlags.CreateInstance、BindingFlags.GetProperty 或 BindingFlags.SetProperty 之一的话,或过滤掉所有参数数量不相等的方法。因此,如果希望使用 PowerBinder 得到可选参数或 params 参数,需要设置以上的四个标志之一才可以,当然,BindingFlags.OptionalParamBinding 也是不能忘记的。

2.6 实现 SelectProperty 方法

相对于方法的选择,属性的选择简单了很多,只要考虑属性的类型和索引参数就可以了,可选参数、params 参数和泛型等复杂的东西全部与属性无关。其流程图如下所示:

C# 使用 Binder 类自定义反射 update 2013.1.26

其中用到的属性类型匹配类似于 BindToField 中的实现,索引参数的匹配则类似于方法参数的匹配,这里就不详细解释了。

以上就是 PowerBinder 的实现原理,从 Binder 类的实现过程中,也可以看到反射的效率为什么这么低下——需要由 RuntimeType 类选出特定类型的所有字段、属性或方法,然后根据名称进行第一次过滤,再由 Binder 类通过各种复杂的判断才能够得到反射的结果。

PowerBinder 类包含了两个静态属性:DefaultBinder 和 CastBinder,其中 DefaultBinder 不支持强制类型转换,CastBinder 则提供了对强制类型转换的支持,泛型方法和用户自定义类型转换是两个类支持的,所有源代码可见 PowerBinder.cs

相关文章:

  • 2021-12-03
  • 2022-02-13
  • 2022-12-23
  • 2021-07-06
  • 2021-08-01
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2021-10-26
  • 2022-12-23
  • 2022-12-23
  • 2021-07-12
  • 2022-12-23
  • 2021-09-06
相关资源
相似解决方案