【问题标题】:If has Method/extension method, then Call it如果有方法/扩展方法,则调用它
【发布时间】:2014-07-10 10:41:45
【问题描述】:

我正在为字典创建一个ToDebugString() 方法,但我也希望它对任何适用于该类型的项目使用ToDebugString() 方法。

由于 ToDebugString() 有时被实现为原生 .NET 类型(如字典和列表)的扩展方法,因此我无法检查该方法是否存在。我只是将扩展方法放在一个名为 ExtensionMethods 的类中,所以我可能只需要在一个额外的类中进行搜索。

兴趣点在这里:

ToDebugString() 抱怨类型参数。此外,由于Value 是泛型类型,它不会自动建议ToDebugString() 方法,所以我认为那里也存在问题。

kv.Value.HasMethod("ToDebugString") ? kv.Value.ToDebugString() : kv.Value.ToString()

如果我不使用本机 .NET 类型,我认为实现一个通用接口将是解决方案。


这是完整的sn-p:

// via: http://stackoverflow.com/a/5114514/796832
public static bool HasMethod(this object objectToCheck, string methodName) {
    var type = objectToCheck.GetType();
    return type.GetMethod(methodName) != null;
} 

// Convert Dictionary to string
// via: http://stackoverflow.com/a/5899291/796832
public static string ToDebugString<TKey, TValue>(this IDictionary<TKey, TValue> dictionary)
{
    return "{" + string.Join(", ", dictionary.Select(kv => kv.Key.ToString() + "=" + (kv.Value.HasMethod("ToDebugString") ? kv.Value.ToDebugString() : kv.Value.ToString())).ToArray()) + "}";
}

还有here are a small tests 我试图让HasMethod() 给出正确的答案。

【问题讨论】:

  • typeof(ExtensionMethods).HasMethod() 目前正在寻找Type 类型上的方法,因为您将其作为对象传入并在其上调用GetType(),这将返回typeof(Type)
  • @DanBryant 我试图关注this answer,它做同样的事情。
  • 是的,但是typeof(ExtensionMethods).GetType() == typeof(Type),所以如果你说typeof(ExtensionMethods).GetType().GetMethod(),你正在寻找一个Type 类型的方法。您需要更改您的助手 HasMethod 以允许直接检查类型而不是实例的类型。
  • @MLM,看看这个答案 - stackoverflow.com/questions/299515/…。扩展方法不属于它们扩展的类型。它们属于定义它们的类。
  • @aleksey.berezan 感谢您提醒我这个问题。我现在有一个有效的方法HasMethodOrExtensionMethod,但这解决了一半的问题。如果该方法不存在,它将无法编译,因为我试图调用一个不存在的方法。请参阅问题的更新部分。

标签: c# generics extension-methods


【解决方案1】:

您的扩展方法没有被调用的原因是因为扩展方法属于定义它们的类型,所以这样的调用:

"Hello world".MyExtensionMethod()

引擎盖下被转换为:

ExtensionMethods.MyExtensionMethod("Hello world"));// "Hello world".MyExtensionMethod()

This topic 有一些代码示例如何获取特定类的所有扩展方法,我对代码进行了一些扩展,下面是按名称运行扩展方法的代码:

    // the utility code

    internal static class ExtensionMethodsHelper
    {
        private static readonly ConcurrentDictionary<Type, IDictionary<string, MethodInfo>> methodsMap = new ConcurrentDictionary<Type, IDictionary<string, MethodInfo>>();

        [MethodImpl(MethodImplOptions.Synchronized)]
        public static MethodInfo GetExtensionMethodOrNull(Type type, string methodName)
        {
            var methodsForType = methodsMap.GetOrAdd(type, GetExtensionMethodsForType);
            return methodsForType.ContainsKey(methodName)
                ? methodsForType[methodName]
                : null;
        }

        private static IDictionary<string, MethodInfo> GetExtensionMethodsForType(Type extendedType)
        {
            // WARNING! Two methods with the same name won't work here
            // for sake of example I ignore this fact
            // but you'll have to do something with that

            return AppDomain.CurrentDomain
                            .GetAssemblies()
                            .Select(asm => GetExtensionMethods(asm, extendedType))
                            .Aggregate((a, b) => a.Union(b))
                            .ToDictionary(mi => mi.Name, mi => mi);
        }

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

    // example: class with extension methods


    public static class ExtensionMethods
    {
        public static string MyExtensionMethod(this string myString)
        {
            return "ranextension on string '" + myString + "'";
        }
    }

    // example: usage

    internal class Program
    {
        private static void Main()
        {
            var mi = ExtensionMethodsHelper.GetExtensionMethodOrNull(typeof(string), "MyExtensionMethod");
            if (mi != null)
            {
                Console.WriteLine(mi.Invoke(null, new object[] { "hello world" }));
            }
            else
            {
                Console.WriteLine("did't find extension method with name " + "MyExtensionMethod");
            }
        }
    }

更新
我们来看这段代码: myTest.HasMethodOrExtensionMethod("MyExtensionMethod") ? myTest.MyExtensionMethod() :“没跑”

它不会编译。如何让它工作。

  // utility code
  public static class ExtensionMethods
  {
      public static string MyExtensionMethod(this string myString)
      {
          return "ranextension on string '" + myString + "'";
      }

      public static object InvokeExtensionMethod(this object instance, string methodName, params object[] arguments)
      {
          if (instance == null) throw new ArgumentNullException("instance");

          MethodInfo mi = ExtensionMethodsHelper.GetExtensionMethodOrNull(instance.GetType(), methodName);
          if (mi == null)
          {
              string message = string.Format("Unable to find '{0}' extension method in '{1}' class.", methodName, instance);
              throw new InvalidOperationException(message);
          }

          return mi.Invoke(null, new[] { instance }.Concat(arguments).ToArray());
      }
  }

  // example usage    
  Console.WriteLine("hey".InvokeExtensionMethod("MyExtensionMethod"));

【讨论】:

  • 清理你的答案并更精确一点,以便我接受它。我已经用我使用的最终解决方案更新了问题。
【解决方案2】:

理解扩展方法的关键是它们属于声明它们的类,而不是它们扩展的类。因此,如果您在类上搜索扩展方法,它正在扩展您期望的位置,它不会在那里。

感谢aleksey.berezan 的评论再次提醒我this question and answer 有一个很好的方式来获取扩展方法。

解决方案:

这是完整的清理解决方案。这段代码在我的项目中也是available hereRadius: a Unity 3D project, on GitHub

它通过检查对象类本身中的ToDebugString() 来工作。然后在ExtensionMethods 类中搜索ToDebugString() 扩展方法。如果也失败了,它只使用普通的ToString()

// Convert Dictionary to string
// via: https://stackoverflow.com/a/5899291/796832
public static string ToDebugString<TKey, TValue>(this IDictionary<TKey, TValue> dictionary)
{
    return "{" + string.Join(", ", dictionary.Select(kv => GetToDebugString(kv.Key) + "=" + GetToDebugString(kv.Value)).ToArray()) + "}";
}

static string GetToDebugString<T>(T objectToGetStringFrom)
{
    // This will try to call the `ToDebugString()` method from the class first
    // Then try to call `ToDebugString()` if it has an extension method in ExtensionMethods class
    // Otherwise just use the plain old `ToString()`

    // Get the MethodInfo
    // This will check in the class itself for the method
    var mi = objectToGetStringFrom.GetMethodOrNull("ToDebugString"); 

    string keyString = "";

    if(mi != null)
        // Get string from method in class
        keyString = (string)mi.Invoke(objectToGetStringFrom, null);
    else
    {
        // Try and find an extension method
        mi = objectToGetStringFrom.GetExtensionMethodOrNull("ToDebugString");

        if(mi != null)
            // Get the string from the extension method
            keyString = (string)mi.Invoke(null, new object[] {objectToGetStringFrom});
        else
            // Otherwise just get the normal ToString
            keyString = objectToGetStringFrom.ToString();
    }

    return keyString;
}

// ------------------------------------------------------------
// ------------------------------------------------------------

// via: https://stackoverflow.com/a/299526/796832
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;
}

public static MethodInfo GetMethodOrNull(this object objectToCheck, string methodName)
{
    // Get MethodInfo if it is available in the class
    // Usage:
    //      string myString = "testing";
    //      var mi = myString.GetMethodOrNull("ToDebugString"); 
    //      string keyString = mi != null ? (string)mi.Invoke(myString, null) : myString.ToString();

    var type = objectToCheck.GetType();
    MethodInfo method = type.GetMethod(methodName);
    if(method != null)
        return method;

    return null;
}

public static MethodInfo GetExtensionMethodOrNull(this object objectToCheck, string methodName)
{
    // Get MethodInfo if it available as an extension method in the ExtensionMethods class
    // Usage:
    //      string myString = "testing";
    //      var mi = myString.GetMethodOrNull("ToDebugString"); 
    //      string keyString = mi != null ? (string)mi.Invoke(null, new object[] {myString}); : myString.ToString();

    Assembly thisAssembly = typeof(ExtensionMethods).Assembly;
    foreach (MethodInfo methodEntry in GetExtensionMethods(thisAssembly, objectToCheck.GetType()))
        if(methodName == methodEntry.Name)
            return methodEntry;

    return null;
}

如果您在其他地方有扩展方法,请务必在GetExtensionMethodOrNull() 中编辑此行:

Assembly thisAssembly = typeof(ExtensionMethods).Assembly;

【讨论】:

    【解决方案3】:

    您对 GetMethod 的调用失败,因为您正在寻找的方法是静态的,并且您没有在 GetMethod 调用中包含该标志。试试这个:

      public static bool HasMethod(this object objectToCheck, string methodName)
      {
         BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
    
         var type = objectToCheck.GetType();
         return type.GetMethod(methodName, flags) != null;
      } 
    

    【讨论】:

    • 我在所有使用带有标志的更新函数的测试中仍然得到“didnotrun”。
    猜你喜欢
    • 1970-01-01
    • 2015-07-02
    • 2021-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-23
    • 2016-11-25
    • 1970-01-01
    相关资源
    最近更新 更多