鉴别扩展方法的思考
在C#中有一种使用reflection技术来确定一个方法是否已经作为扩展方法添加到类中?
给定一个如下所示的扩展方法是否可以确定Reverse()已经添加到string类?
public static class StringExtensions { public static string Reverse(this string value) { char[] cArray = value.ToCharArray(); Array.Reverse(cArray); return new string(cArray); } }
我们正在寻找一种机制,在unit testing中确定扩展方法是由开发人员适当添加的。 这样做的一个原因是开发人员可能会将类似的方法添加到实际的类中,如果是的话,编译器会select该方法。
您必须查看扩展方法可能定义的所有程序集。
查找使用ExtensionAttribute
装饰的类,然后查找使用ExtensionAttribute
装饰的类中的方法。 然后检查第一个参数的types,看它是否与你感兴趣的types相匹配。
这是一些完整的代码。 这可能会更严格(不检查types是否嵌套,或至less有一个参数),但它应该给你一个帮助。
using System; using System.Runtime.CompilerServices; using System.Reflection; using System.Linq; using System.Collections.Generic; public static class FirstExtensions { public static void Foo(this string x) {} public static void Bar(string x) {} // Not an ext. method public static void Baz(this int x) {} // Not on string } public static class SecondExtensions { public static void Quux(this string x) {} } public class Test { static void Main() { Assembly thisAssembly = typeof(Test).Assembly; foreach (MethodInfo method in GetExtensionMethods(thisAssembly, typeof(string))) { Console.WriteLine(method); } } static IEnumerable<MethodInfo> GetExtensionMethods(Assembly assembly, Type extendedType) { var query = from type in assembly.GetTypes() where type.IsSealed && !type.IsGenericType && !type.IsNested from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where method.IsDefined(typeof(ExtensionAttribute), false) where method.GetParameters()[0].ParameterType == extendedType select method; return query; } }
基于约翰Skeet的答案,我已经创build了我自己的System.Typetypes的扩展。
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; namespace System { public static class TypeExtension { /// <summary> /// This Methode extends the System.Type-type to get all extended methods. It searches hereby in all assemblies which are known by the current AppDomain. /// </summary> /// <remarks> /// Insired by Jon Skeet from his answer on http://stackoverflow.com/questions/299515/c-sharp-reflection-to-identify-extension-methods /// </remarks> /// <returns>returns MethodInfo[] with the extended Method</returns> public static MethodInfo[] GetExtensionMethods(this Type t) { List<Type> AssTypes = new List<Type>(); foreach (Assembly item in AppDomain.CurrentDomain.GetAssemblies()) { AssTypes.AddRange(item.GetTypes()); } var query = from type in AssTypes where type.IsSealed && !type.IsGenericType && !type.IsNested from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where method.IsDefined(typeof(ExtensionAttribute), false) where method.GetParameters()[0].ParameterType == t select method; return query.ToArray<MethodInfo>(); } /// <summary> /// Extends the System.Type-type to search for a given extended MethodeName. /// </summary> /// <param name="MethodeName">Name of the Methode</param> /// <returns>the found Methode or null</returns> public static MethodInfo GetExtensionMethod(this Type t, string MethodeName) { var mi = from methode in t.GetExtensionMethods() where methode.Name == MethodeName select methode; if (mi.Count<MethodInfo>() <= 0) return null; else return mi.First<MethodInfo>(); } } }
它从当前AppDomain获取所有程序集并search扩展方法。
用法:
Type t = typeof(Type); MethodInfo[] extendedMethods = t.GetExtensionMethods(); MethodInfo extendedMethodInfo = t.GetExtensionMethod("GetExtensionMethods");
下一步将是扩展System.Type与方法,返回所有的方法(也是“普通”与扩展的方法)
这将返回一个在特定types中定义的所有扩展方法的列表,包括通用的方法:
public static IEnumerable<KeyValuePair<Type, MethodInfo>> GetExtensionMethodsDefinedInType(this Type t) { if (!t.IsSealed || t.IsGenericType || t.IsNested) return Enumerable.Empty<KeyValuePair<Type, MethodInfo>>(); var methods = t.GetMethods(BindingFlags.Public | BindingFlags.Static) .Where(m => m.IsDefined(typeof(ExtensionAttribute), false)); List<KeyValuePair<Type, MethodInfo>> pairs = new List<KeyValuePair<Type, MethodInfo>>(); foreach (var m in methods) { var parameters = m.GetParameters(); if (parameters.Length > 0) { if (parameters[0].ParameterType.IsGenericParameter) { if (m.ContainsGenericParameters) { var genericParameters = m.GetGenericArguments(); Type genericParam = genericParameters[parameters[0].ParameterType.GenericParameterPosition]; foreach (var constraint in genericParam.GetGenericParameterConstraints()) pairs.Add(new KeyValuePair<Type, MethodInfo>(parameters[0].ParameterType, m)); } } else pairs.Add(new KeyValuePair<Type, MethodInfo>(parameters[0].ParameterType, m)); } } return pairs; }
这只有一个问题:返回的types与您期望的typeof(..)不一样,因为它是一个通用的参数types。 为了find给定types的所有扩展方法,你必须比较types的所有基types和接口的GUID,如:
public List<MethodInfo> GetExtensionMethodsOf(Type t) { List<MethodInfo> methods = new List<MethodInfo>(); Type cur = t; while (cur != null) { TypeInfo tInfo; if (typeInfo.TryGetValue(cur.GUID, out tInfo)) methods.AddRange(tInfo.ExtensionMethods); foreach (var iface in cur.GetInterfaces()) { if (typeInfo.TryGetValue(iface.GUID, out tInfo)) methods.AddRange(tInfo.ExtensionMethods); } cur = cur.BaseType; } return methods; }
完成:
我保留一个types信息对象的字典,当迭代所有的程序集的所有types时,我build立:
private Dictionary<Guid, TypeInfo> typeInfo = new Dictionary<Guid, TypeInfo>();
TypeInfo
被定义为:
public class TypeInfo { public TypeInfo() { ExtensionMethods = new List<MethodInfo>(); } public List<ConstructorInfo> Constructors { get; set; } public List<FieldInfo> Fields { get; set; } public List<PropertyInfo> Properties { get; set; } public List<MethodInfo> Methods { get; set; } public List<MethodInfo> ExtensionMethods { get; set; } }
澄清一点乔恩掩饰了…“添加”一个扩展方法的类不以任何方式改变类。 这只是由C#编译器执行的一些小动作。
所以,用你的例子,你可以写
string rev = myStr.Reverse();
但写入程序集的MSIL将与您写入的完全一样:
string rev = StringExtensions.Reverse(myStr);
编译器只是让你愚弄自己,认为你正在调用一个string的方法。
这样做的一个原因是开发人员可能会将类似的方法添加到实际的类中,如果是的话,编译器会select该方法。
- 假设定义了一个扩展方法void Foo(this Customer someCustomer) 。
- 假设客户被修改,并添加void Foo()方法。
- 然后,客户的新方法将覆盖/隐藏扩展方法。
在这一点上调用旧的Foo方法的唯一方法是:
CustomerExtension.Foo(myCustomer);
void Main() { var test = new Test(); var testWithMethod = new TestWithExtensionMethod(); Tools.IsExtensionMethodCall(() => test.Method()).Dump(); Tools.IsExtensionMethodCall(() => testWithMethod.Method()).Dump(); } public class Test { public void Method() { } } public class TestWithExtensionMethod { } public static class Extensions { public static void Method(this TestWithExtensionMethod test) { } } public static class Tools { public static MethodInfo GetCalledMethodInfo(Expression<Action> expr) { var methodCall = expr.Body as MethodCallExpression; return methodCall.Method; } public static bool IsExtensionMethodCall(Expression<Action> expr) { var methodInfo = GetCalledMethodInfo(expr); return methodInfo.IsStatic; } }
输出:
假
真正