Reputation: 3688
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.
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. I am only putting extension methods in a single class called ExtensionMethods
so I may only have to search in one additional class.
The point of interest is here:
The ToDebugString()
complains about type arguments. Also since Value
is a generic type, it doesn't auto-suggest the ToDebugString()
method so I assume there are problems there as well.
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.
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.
Upvotes: 3
Views: 2110
Reputation: 3688
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.
Here is the complete cleaned up solution. This code is also available here in my project, Radius: a Unity 3D project, on GitHub.
It works by checking for ToDebugString()
in the object class itself. Then searches for ToDebugString()
extension methods in the ExtensionMethods
class. If that fails as well, it just uses the normal 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()
:
Assembly thisAssembly = typeof(ExtensionMethods).Assembly;
Upvotes: 2
Reputation: 4567
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() :
"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"));
Upvotes: 4
Reputation: 359
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. 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;
}
Upvotes: -1