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

在利用 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 类为我们做的一些工作。

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

  • 如果设置了 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 方法的实现流程图,虽然看起来不是很复杂,但其中的每一个步骤都需要做很多的工作。

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 参数和泛型等复杂的东西全部与属性无关。其流程图如下所示:

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

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

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

转载于:https://www.cnblogs.com/cyjb/archive/2013/01/22/PowerBinder.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值