【问题标题】:Reflection to Identify Extension Methods反思以识别扩展方法
【发布时间】:2008-11-18 17:40:09
【问题描述】:

在 C# 中是否有一种技术使用反射来确定方法是否已作为扩展方法添加到类中?

给定如下所示的扩展方法,是否可以确定已将 Reverse() 添加到字符串类中?

public static class StringExtensions
{
    public static string Reverse(this string value)
    {
        char[] cArray = value.ToCharArray();
        Array.Reverse(cArray);
        return new string(cArray);
    }
}

我们正在寻找一种机制来确定在单元测试中扩展方法是由开发人员适当添加的。尝试这样做的一个原因是开发人员可能会将类似的方法添加到实际的类中,如果是,编译器会选择该方法。

【问题讨论】:

    标签: c# reflection extension-methods


    【解决方案1】:

    您必须查看所有可能定义扩展方法的程序集。

    查找用ExtensionAttribute 修饰的类,然后在该类中ExtensionAttribute 修饰的方法。然后检查第一个参数的类型,看是否匹配你感兴趣的类型。

    这里有一些完整的代码。它可能更严格(它不是检查类型是否嵌套,或者是否存在至少一个参数),但它应该可以帮助您。

    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;
        }
    }
    

    【讨论】:

    • 不错的代码。您可以使用以下事实排除一堆方法: 扩展方法必须在非泛型静态类中定义。 !type.IsGenericType && type.IsSealed
    • 没错,这是一个相当简单的测试。很遗憾,没有等效的 IsDefined for Type :) 我将使用该代码编辑答案。
    • 如果扩展类型是泛型则不起作用。例如,IQueryable 的扩展方法将匹配,但对于 IQueryable 则不匹配。我认为它在 ParameterType 上失败了。
    • @Seb:是的,要使其适用于泛型方法需要付出更多的努力。可行,但很棘手。
    • @JonSkeet 嗯,我明白了。但是它留下了任何人都可以将[Extension] 属性应用于顶级静态非泛型类中的方法并且仍然不是扩展方法的选项:) 不是吗?
    【解决方案2】:

    根据 John Skeet 的回答,我创建了自己的 System.Type-type 扩展。

    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 中获取所有程序集并搜索扩展方法。

    用法:

    Type t = typeof(Type);
    MethodInfo[] extendedMethods = t.GetExtensionMethods();
    MethodInfo extendedMethodInfo = t.GetExtensionMethod("GetExtensionMethods");
    

    下一步是用方法扩展 System.Type,它返回所有方法(也包括扩展方法的“正常”方法)

    【讨论】:

      【解决方案3】:

      这将返回以某种类型定义的所有扩展方法的列表,包括通用方法:

      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;
      }
      

      这只有一个问题:返回的类型与 typeof(..) 所期望的不同,因为它是一个泛型参数类型。为了找到给定类型的所有扩展方法,您必须比较该类型的所有基本类型和接口的 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;
      }
      

      要完整:

      我保留了一个类型信息对象的字典,这是我在迭代所有程序集的所有类型时构建的:

      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; }
      }
      

      【讨论】:

      • 就目前而言,您的代码为多个约束添加了多个相同的 KVP,并且添加 (constraint, m} 的“明显”修复(实际上对于单个约束更正确)是不正确的,因为约束是“和”不是“或”。
      • 其实,从编译器的角度来看,因为它没有将约束视为差异化因素,所以当前代码有一些优点:只需去掉 foreach 约束这一行。但这并不能帮助我们通过反射实现扩展方法:-(
      【解决方案4】:

      为了澄清 Jon 忽略的一点……“添加”一个扩展方法到一个类不会以任何方式改变这个类。这只是 C# 编译器执行的一点点旋转。

      所以,用你的例子,你可以写

      string rev = myStr.Reverse();
      

      但是写入程序集的 MSIL 将与您编写的完全一样:

      string rev = StringExtensions.Reverse(myStr);
      

      编译器只是让你自欺欺人地认为你正在调用 String 的方法。

      【讨论】:

      • 是的。我完全知道编译器正在使用一些“魔法”来隐藏细节。这就是我们有兴趣在单元测试中检测该方法是否为扩展方法的原因之一。
      【解决方案5】:

      尝试这样做的一个原因是开发人员可能会将类似的方法添加到实际的类中,如果是,编译器会选择该方法。

      • 假设定义了一个扩展方法void Foo(this Customer someCustomer)
      • 还假设修改了 Customer 并添加了方法 void Foo()
      • 然后,Customer 上的新方法将覆盖/隐藏扩展方法。

      此时调用旧 Foo 方法的唯一方法是:

      CustomerExtension.Foo(myCustomer);
      

      【讨论】:

        【解决方案6】:
        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;
            }
        }
        

        输出:

        错误

        是的

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-02-05
          • 2022-09-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-02
          相关资源
          最近更新 更多