简体   繁体   English

如果有方法/扩展方法,则调用它

[英]If has Method/extension method, then Call it

I am making a ToDebugString() method for dictionaries but I also want it to use the ToDebugString() method for any items if it is available for that type. 我正在为字典制作一个ToDebugString()方法,但我也希望它对任何项目使用ToDebugString()方法,如果它可用于该类型。

Since ToDebugString() is sometimes implemented as a extension method for native .NET types (like dictionary and list), I am having trouble checking for the method existence. 由于ToDebugString()有时被实现为本机.NET类型(如字典和列表)的扩展方法,因此我无法检查方法是否存在。 I am only putting extension methods in a single class called ExtensionMethods so I may only have to search in one additional class. 我只将扩展方法放在一个名为ExtensionMethods类中,所以我可能只需要在另一个类中搜索。

The point of interest is here: 兴趣点在这里:

The ToDebugString() complains about type arguments. ToDebugString()抱怨类型参数。 Also since Value is a generic type, it doesn't auto-suggest the ToDebugString() method so I assume there are problems there as well. 此外,由于Value是泛型类型,因此它不会自动建议ToDebugString()方法,所以我认为那里也存在问题。

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

If I wasn't using native .NET types, implementing a common interface I think would be the solution. 如果我没有使用本机.NET类型,我认为实现一个通用接口将是解决方案。


Here is the full snippet: 这是完整的片段:

// 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()) + "}";
}

Also here are a small tests I made for trying to get HasMethod() to give the correct. 此外我还试图让HasMethod()给出正确的测试。

The reason why your extension method does not get called is because extension methods belong to types where they are defined so such calls: 你的扩展方法没有被调用的原因是因为扩展方法属于定义它们的类型,所以这样的调用:

"Hello world".MyExtensionMethod()

under the hood gets converted to: 引擎盖下转换为:

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

This topic has some code example how to get all the extension methods for specific class, I've extended the code a bit and here's the code for running extension method by name: 这个主题有一些代码示例如何获取特定类的所有扩展方法,我已经扩展了一些代码,这里是按名称运行扩展方法的代码:

    // 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");
            }
        }
    }

Update 更新
Let's take this piece of code: myTest.HasMethodOrExtensionMethod("MyExtensionMethod") ? myTest.MyExtensionMethod() : 我们来看看这段代码: myTest.HasMethodOrExtensionMethod("MyExtensionMethod") ? myTest.MyExtensionMethod() : myTest.HasMethodOrExtensionMethod("MyExtensionMethod") ? myTest.MyExtensionMethod() : "didnotrun" myTest.HasMethodOrExtensionMethod("MyExtensionMethod") ? myTest.MyExtensionMethod() : “didnotrun”

It does not compile. 它不编译。 How to get it working. 如何让它工作。

  // 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"));

The key to understanding extension methods is that they belong to the class they are declared in, not the class they are extending. 理解扩展方法的关键是它们属于它们声明的类,而不是它们扩展的类。 So if you search for the extension method on the class it is extending where you expect it to be, it will not be there. 因此,如果您在类上搜索扩展方法,它将扩展到您期望的位置,它将不会存在。

Thanks to aleksey.berezan 's comment for reminding me again of this question and answer which has a great way to grab extension methods. 感谢aleksey.berezan的评论再次提醒我这个问题和答案 ,这有很好的方法来获取扩展方法。

Solution: 解:

Here is the complete cleaned up solution. 这是完整的清理解决方案。 This code is also available here in my project, Radius: a Unity 3D project, on GitHub . 这个代码也可以在我的项目Radius:一个Unity 3D项目,GitHub上找到

It works by checking for ToDebugString() in the object class itself. 它的工作原理是检查对象类本身的ToDebugString() Then searches for ToDebugString() extension methods in the ExtensionMethods class. 然后在ExtensionMethods类中搜索ToDebugString()扩展方法。 If that fails as well, it just uses the normal ToString() . 如果失败,它只使用正常的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;
}

If you have your extension methods somewhere else, be sure to edit this line in GetExtensionMethodOrNull() : 如果您在其他地方有扩展方法,请务必在GetExtensionMethodOrNull()编辑此行:

Assembly thisAssembly = typeof(ExtensionMethods).Assembly;

Your call to GetMethod is failing because the method you are looking for is static, and you are not including that flag in the GetMethod call. 您对GetMethod的调用失败,因为您要查找的方法是静态的,并且您没有在GetMethod调用中包含该标志。 Try this: 试试这个:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM