I had expected to find an answer easily to this problem, but I didn't.
I'd like to know if it is possible to determine whether a method has the keyword 'override' attributed to it, given its instance of MethodInfo.
I was thinking maybe the following would achieve that:
/// <summary> Returns whether the specified methodInfo is attributed with the keyword 'override'. </summary>
public static bool IsOverriding(this MethodInfo methodInfo)
{
if (methodInfo == null) throw new ArgumentNullException();
return methodInfo.DeclaringType != methodInfo.GetBaseDefinition().DeclaringType;
}
I've sucessfully tested some non-virtual, virtual and abstract examples, but I feel like I'm missing some scenarios, maybe with hiding or generics(although I can't figure out how that would come into play).
I try to find this thing also. From the question, you give the idea to get IsOverriding work for override keyword. However for hiding I try to create IsHiding for keyword new. Here is the basic C# OOP:
override only used when the based method contain abstract or virtual modifier.
new is used to hide based method with the same name but...
new cannot be apply to abstract base method because it generate compiler error.
However the interesting part is new also can be applied to method if the base method contain virtual.
The inseresting part is we can hide or override virtual method. We know that GetBaseDefinition() will return the base MethodInfo if we override a virtual method. but the key to differentiate it is the GetBaseDefinition() will return the same MethodInfo instead of it base MethodInfo if we hiding virtual method.
override keyword is a must while new is only used to suppress the warning message. So we can diffrentiate override and new by the IsAbstract and IsVirtual combine with DeclaringType and BaseType.
public static bool IsOverriding(this MethodInfo methodInfo)
{
if (methodInfo == null) throw new ArgumentNullException("methodInfo");
return methodInfo.DeclaringType != methodInfo.GetBaseDefinition().DeclaringType;
}
public static bool IsHiding(this MethodInfo methodInfo)
{
if (methodInfo == null) throw new ArgumentNullException("methodInfo");
if (methodInfo.DeclaringType == methodInfo.GetBaseDefinition().DeclaringType)
{
var baseType = methodInfo.DeclaringType.BaseType;
if (baseType != null)
{
MethodInfo hiddenBaseMethodInfo = null;
var methods = baseType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static);
foreach (var mi in methods)
if (mi.Name == methodInfo.Name)
{
var miParams = mi.GetParameters();
var methodInfoParams = methodInfo.GetParameters();
if (miParams.Length == methodInfoParams.Length)
{
var i = 0;
for (; i < miParams.Length; i++)
{
if (miParams[i].ParameterType != methodInfoParams[i].ParameterType
|| ((miParams[i].Attributes ^ methodInfoParams[i].Attributes).HasFlag(ParameterAttributes.Out))) break;
// Simplified from:
//if (miParams[i].ParameterType != methodInfoParams[i].ParameterType
// || (miParams[i].Attributes.HasFlag(ParameterAttributes.Out) && !methodInfoParams[i].Attributes.HasFlag(ParameterAttributes.Out))
// || !(miParams[i].Attributes.HasFlag(ParameterAttributes.Out) && methodInfoParams[i].Attributes.HasFlag(ParameterAttributes.Out))) break;
}
if (i == miParams.Length)
{
hiddenBaseMethodInfo = mi;
break;
}
}
}
if (hiddenBaseMethodInfo != null && !hiddenBaseMethodInfo.IsPrivate) return true;
}
}
return false;
}
I test it using the simple inheritance and it works. I don't think about generic method..yet..
EDIT: I just change the code above because I forgot the idea about inherited type can contain newly declared method which is should not threated as new. IsHiding() will first make sure it have the same DeclaringType (it seems like new) but need to look at the base declaring types by DeclaringType.BaseType if a method with the same name exist.
Note that because of there is no BindingFlags.DeclaredOnly, GetMethod() will search through the entire base types, so no need to recursively search to each base types. BindingFlags.FlattenHierarchy is used to include static method in abstract-abstract base class like this:
public abstract class A
{
public static void Stat() { }
}
public abstract class B : A
{
}
public class C: B
{
public new static void Stat() { }
}
EDIT: I just fix the IsHiding() above to check for base method overloads and prevent AmbiguousMatchException by using GetMethods() instead of GetMethod(). The overloads tested to work with combination with different parameters, mix with ref, out, params and optional parameter. Signature for overloads being compared based on parameter count, parameter types and its modifier:
ref or out included in the signature but both cannot be overloaded to each other.
return type, optional (default value) and params on right most parameter should be ignored
ref compared by the ParameterType itself (see the ending '&' during debugging) and no need to compare by the IsByRef while the out compared by the Attributes flag. I am using simplified expression bitwise XOR to skip the loop if and only if one of the attributes has flag Out which makes the signature different. Don't confused with HasFlag in .NET 4, it just want to make sure Out bit is 1 by the XOR result.
Well, I don't see how that would come into play either. Your code there does indeed determine whether a method is defined or overriden.
In the case of hiding, the declaring type is the one that hides the method via new.
In the case of generics, all methods are defined by the template class.
You can try this
public static bool IsOverriding(this MethodInfo methodInfo)
{
if (methodInfo == null) throw new ArgumentNullException();
return methodInfo.GetBaseDefinition() != methodInfo;
}
Related
We have a custom FileExtensionAttribute which we decorate our model classes which are based on file persistence with. It is defined as follows:
[AttributeUsage(AttributeTargets.Class, AllowMultiple=true, Inherited=true)]
public class FileExtensionAttribute : Attribute
{
public FileExtensionAttribute(string fileExtension)
{
FileExtension = fileExtension;
}
public readonly string FileExtension;
}
We've also created the following extension methods to make retrieving those extensions more convenient:
public static class FileExtensionAttributeHelper
{
public static IEnumerable<string> GetFileExtensions(this Type type)
{
return type.CustomAttributes
.OfType<FileExtensionAttribute>()
.Select(fileExtensionAttribute => fileExtensionAttribute.FileExtension);
}
public static string GetPrimaryFileExtension(this Type type)
{
return GetFileExtensions(type).FirstOrDefault();
}
}
In the above, for types which don't have the attribute specified, the two methods return an empty enumeration or null respectively. However, we would like to be more proactive in stopping such calls in the first place.
While we can easily throw an exception if no such attributes are found on the specified type, I'm wondering if there's a way to restrict the calling of the extension methods to only support types which have that attribute set in the first place so it's a compile-time error and not something that has to be dealt with at run-time.
So is it possible to restrict extension methods to only support types with a given attribute? If so, how?
Note: I'm thinking this may not be possible in pure C#, but perhaps something like PostSharp can be used for this.
This is not currently supported. Extension methods are limiting, but can be extremely powerful. I am most curious why getting an empty list back is a problem, I would assume that would be ideal. If it is empty or null then do nothing, not a big deal -- life goes on.
To more directly answer your question, no. You cannot restrict extension methods by attribute for compile time errors.
PostSharp can indeed help you.
Outline:
Create AssemblyLevelAspect that would search using ReflectionSearch for all uses of your extension methods in the assembly. This will give a list of methods that call those extension methods.
For all these methods, get the syntax tree using ISyntaxReflectionService. It is IL syntax tree not the source code itself.
Search for patterns like typeof(X).GetFileExtensions() and variable.GetType.GetFileExtensions() and validate that the passed type has FileExtension attribute.
Write a compile time error if incorrect usage is found.
Source:
[MulticastAttributeUsage(PersistMetaData = true)]
public class FileExtensionValidationPolicy : AssemblyLevelAspect
{
public override bool CompileTimeValidate( Assembly assembly )
{
ISyntaxReflectionService reflectionService = PostSharpEnvironment.CurrentProject.GetService<ISyntaxReflectionService>();
MethodInfo[] validatedMethods = new[]
{
typeof(FileExtensionAttributeHelper).GetMethod( "GetFileExtensions", BindingFlags.Public | BindingFlags.Static ),
typeof(FileExtensionAttributeHelper).GetMethod( "GetPrimaryFileExtension", BindingFlags.Public | BindingFlags.Static )
};
MethodBase[] referencingMethods =
validatedMethods
.SelectMany( ReflectionSearch.GetMethodsUsingDeclaration )
.Select( r => r.UsingMethod )
.Where( m => !validatedMethods.Contains( m ) )
.Distinct()
.ToArray();
foreach ( MethodBase userMethod in referencingMethods )
{
ISyntaxMethodBody body = reflectionService.GetMethodBody( userMethod, SyntaxAbstractionLevel.ExpressionTree );
ValidateMethodBody(body, userMethod, validatedMethods);
}
return false;
}
private void ValidateMethodBody(ISyntaxMethodBody methodBody, MethodBase userMethod, MethodInfo[] validatedMethods)
{
MethodBodyValidator validator = new MethodBodyValidator(userMethod, validatedMethods);
validator.VisitMethodBody(methodBody);
}
private class MethodBodyValidator : SyntaxTreeVisitor
{
private MethodBase userMethod;
private MethodInfo[] validatedMethods;
public MethodBodyValidator( MethodBase userMethod, MethodInfo[] validatedMethods )
{
this.userMethod = userMethod;
this.validatedMethods = validatedMethods;
}
public override object VisitMethodCallExpression( IMethodCallExpression expression )
{
foreach ( MethodInfo validatedMethod in this.validatedMethods )
{
if ( validatedMethod != expression.Method )
continue;
this.ValidateTypeOfExpression(validatedMethod, expression.Arguments[0]);
this.ValidateGetTypeExpression(validatedMethod, expression.Arguments[0]);
}
return base.VisitMethodCallExpression( expression );
}
private void ValidateTypeOfExpression(MethodInfo validatedMethod, IExpression expression)
{
IMethodCallExpression callExpression = expression as IMethodCallExpression;
if (callExpression == null)
return;
if (callExpression.Method != typeof(Type).GetMethod("GetTypeFromHandle"))
return;
IMetadataExpression metadataExpression = callExpression.Arguments[0] as IMetadataExpression;
if (metadataExpression == null)
return;
Type type = metadataExpression.Declaration as Type;
if (type == null)
return;
if (!type.GetCustomAttributes(typeof(FileExtensionAttribute)).Any())
{
MessageSource.MessageSink.Write(
new Message(
MessageLocation.Of( this.userMethod ),
SeverityType.Error, "MYERR1",
String.Format( "Calling method {0} on type {1} is not allowed.", validatedMethod, type ),
null, null, null
)
);
}
}
private void ValidateGetTypeExpression(MethodInfo validatedMethod, IExpression expression)
{
IMethodCallExpression callExpression = expression as IMethodCallExpression;
if (callExpression == null)
return;
if (callExpression.Method != typeof(object).GetMethod("GetType"))
return;
IExpression instanceExpression = callExpression.Instance;
Type type = instanceExpression.ReturnType;
if (type == null)
return;
if (!type.GetCustomAttributes(typeof(FileExtensionAttribute)).Any())
{
MessageSource.MessageSink.Write(
new Message(
MessageLocation.Of(this.userMethod),
SeverityType.Error, "MYERR1",
String.Format("Calling method {0} on type {1} is not allowed.", validatedMethod, type),
null, null, null
)
);
}
}
}
}
Usage:
[assembly: FileExtensionValidationPolicy(
AttributeInheritance = MulticastInheritance.Multicast
)]
Notes:
[MulticastAttributeUsage(PersistMetaData = true)] and AttributeInheritance = MulticastInheritance.Multicast are both needed to preserve the attribute on the assembly so that the analysis is performed also on projects that reference the declaring project.
More deep analysis may be needed to correctly handle derived classes and other special cases.
PostSharp Professional license is needed.
Basically, some internal check that happens in the static System.Linq.Expressions.Expression.Bind() method says "Method is not a property accessor" on my property that is clearly a property. Using Reflector, I've minimized the amount of code causing the problem, and I can't for the life of me figure out why this would happen. My only guess is that it has something to do with the fact that the property isn't on the class itself, but I would think this should still work:
I've tried to make a small example with the least amount of code possible. The code below should run in its entirety.
using System;
using System.Reflection;
public class Base
{
public virtual int Id { get; set; }
}
// As you can see, SubClass does not override the Id property of Base.
public class SubClass : Base { }
class Program
{
static void Main(string[] args)
{
// Getting the property directly from the type.
PropertyInfo propertyInfo = typeof(SubClass).GetProperty("Id");
MethodInfo setMethod = propertyInfo.GetSetMethod();
/* Code from here on out is from the System.Linq.Expressions.Bind() method (the one
that accepts a MethodInfo argument). This method causes GetProperty to be called
and retrieve what should be the same PropertyInfo. It fails here, saying something
along the lines of "Method is not a property accessor." which doesn't make sense. */
PropertyInfo propertyInfo2 = GetProperty(setMethod);
}
private static PropertyInfo GetProperty(MethodInfo mi)
{
// Not sure if it matters, but declaringType here is "Base".
Type declaringType = mi.DeclaringType;
BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public;
bindingAttr |= mi.IsStatic ? BindingFlags.Static : BindingFlags.Instance;
foreach (PropertyInfo info in declaringType.GetProperties(bindingAttr))
{
// For the "Id" property, info.CanRead is true, but CheckMethod is false.
if (info.CanRead && CheckMethod(mi, info.GetGetMethod(true)))
return info;
// For the "Id" property, info.CanWrite is true, but CheckMethod is false.
if (info.CanWrite && CheckMethod(mi, info.GetSetMethod(true)))
return info;
}
// This gets thrown after passing by the "Id" property that is the one I'm looking for.
throw new Exception("Method is not a property accessor");
}
private static bool CheckMethod(MethodInfo method, MethodInfo propertyMethod)
{
// These are not equal, so it goes to the next check. In the debugger, they appear identical when I look through the object tree.
if (method == propertyMethod)
return true;
Type declaringType = method.DeclaringType;
return ((declaringType.IsInterface && (method.Name == propertyMethod.Name)) && (declaringType.GetMethod(method.Name) == propertyMethod));
}
}
If anyone could help I would greatly appreciate it! I'm very much lost at this point.
Edit - If you substitute typeof(SubClass) with typeof(Base), it works, so it's something related to that relationship. I guess I could cut the issue off at the root by making an extension method like typeof(SubClass).GetPropertyFromActualClass("Id") but I'm not sure how to perform that check.
The property and method infos of Base and SubClass are indeed not equal, even if SubClass has no own implementation, as can be seen here:
var subPropertyInfo = typeof(SubClass).GetProperty("Id");
var subPropertyInfoSetter = subPropertyInfo.GetSetMethod();
var basePropertyInfo = typeof(Base).GetProperty("Id");
var basePropertyInfoSetter = basePropertyInfo.GetSetMethod();
Console.WriteLine(subPropertyInfo == basePropertyInfo); // false
Console.WriteLine(subPropertyInfoSetter == basePropertyInfoSetter); // false
If you use mi.ReflectedType instead of mi.DeclaringType, the check succeeds:
var type = subPropertyInfoSetter.ReflectedType;
Console.WriteLine(type.GetProperty("Id").GetSetMethod()
== subPropertyInfoSetter); // true
I want to know if something like this is possible: I've overriden a property of a base class, which is auto-implemented. I've supplied logic in the override to resolve "missing" properties against default settings.
Now, I want to use reflection to check whether the default value is used or some "actual" value. In other words, I need to check if base.Property is null, but by using reflection. This doesn't work, it simply gets the sub-class value (which is resolved against defaults, so not null).
var property = this.GetType().GetProperty(e.PropertyName);
if(property.GetValue(this, null) == null))
OnPropertyChanged(e.PropertyName);
Also tried:
var property = this.GetType().BaseType.GetProperty(e.PropertyName);
if(property.GetValue(this, null) == null))
OnPropertyChanged(e.PropertyName);
Is it possible using reflection to access the base class value?
UPDATE:
Following advice from comments, I tried the following, just for kicks.
var method1 = this.GetType().BaseType.GetMethods().First(x => x.Name.Contains(e.PropertyName));
var method = this.GetType().BaseType.GetProperty(e.PropertyName).GetGetMethod();
var methodValue = method1.Invoke(this, null);
Both of these still return the "derived" value, while at the same time base.Property returns null.
It is possible, although as far as I know there's no way to do it without emitting your own IL, basically using the call instruction rather than callvirt.
Note that if you need to go to these lengths to make your design work then that's a sign that you're probably doing something wrong somewhere!
Anyway, here's a contrived example. (Error-checking etc omitted for brevity.)
var derived = new DerivedClass();
Console.WriteLine(derived.GetBaseProperty("Prop")); // displays "BaseProp"
// ...
public class BaseClass
{
public virtual string Prop { get; set;}
}
public class DerivedClass : BaseClass
{
public override string Prop { get; set;}
public DerivedClass()
{
base.Prop = "BaseProp";
this.Prop = "DerivedProp";
}
public object GetBaseProperty(string propName)
{
Type t = this.GetType();
MethodInfo mi = t.BaseType.GetProperty(propName).GetGetMethod();
var dm = new DynamicMethod("getBase_" + propName, typeof(object), new[] { typeof(object) }, t);
ILGenerator il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, mi);
if (mi.ReturnType.IsValueType) il.Emit(OpCodes.Box, mi.ReturnType);
il.Emit(OpCodes.Ret);
var getBase = (Func<object, object>)dm.CreateDelegate(typeof(Func<object, object>));
return getBase(this);
}
}
AFAIK I think it is not possible. The real type of the object is the derived type, and by definition of virtual methods, no matter through which type instance (actual type or base type) you call a method, you will get the overriden implementation.
Having this work any other way would be, at least to me, unexpected behavior.
EDIT: I have tried the following to see if it is actually possible to get to the base implentation:
Type baseType = this.GetType().BaseType;
var methodInfo = baseType.GetMethod("Foo");
string foo = methodInfo.Invoke(this, null); //Derived type implementation
This means that even calling the method through the base type MethodInfo reflection is able to resolve the override and will return the derived implementation. So I think what you are trying is not possible through reflection or at least I can not see a way to do it.
I'm starting to work with dynamic objects in .Net and I can't figure out how to do something.
I have a class that inherits from DynamicObject, and I override the TryInvokeMember method.
e.g.
class MyCustomDynamicClass : DynamicObject
{
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
// I want to know here the type of the generic argument
}
}
And inside that method I want to know the type (if any) of the generic arguments in the invocation.
e.g.
If I invoke the following code, I want to get the value of System.Boolean and System.Int32 inside the overrided method of my dynamic object
dynamic myObject = new MyCustomDynamicClass();
myObject.SomeMethod<bool>("arg");
myObject.SomeOtherMethod<int>("arg");
Currently if I place a breakpoint inside the overrided method I can get the name of the method being invoked ("SomeMethod" and "SomeOtherMethod", and also the values of the arguments, but not the generic types).
How can I get these values?
Thanks!
Actually I looked through the hierarchy of the binder and found a property with the needed values in the internal fields of the object.
The problem is that the property isn't exposed because it uses C#-specific code/classes, therefore the properties must be accessed using Reflection.
I found the code in this japanese blog: http://neue.cc/category/programming (I don't read any japanese, therefore I'm not sure if the author actually describes this same issue
Here's the snippet:
var csharpBinder = binder.GetType().GetInterface("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder");
var typeArgs = (csharpBinder.GetProperty("TypeArguments").GetValue(binder, null) as IList<Type>);
typeArgs is a list containing the types of the generic arguments used when invoking the method.
Hope this helps someone else.
A bit of googling and I have quite generic solution for .NET and Mono:
/// <summary>Framework detection and specific implementations.</summary>
public static class FrameworkTools
{
private static bool _isMono = Type.GetType("Mono.Runtime") != null;
private static Func<InvokeMemberBinder, IList<Type>> _frameworkTypeArgumentsGetter = null;
/// <summary>Gets a value indicating whether application is running under mono runtime.</summary>
public static bool IsMono { get { return _isMono; } }
static FrameworkTools()
{
_frameworkTypeArgumentsGetter = CreateTypeArgumentsGetter();
}
private static Func<InvokeMemberBinder, IList<Type>> CreateTypeArgumentsGetter()
{
if (IsMono)
{
var binderType = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder");
if (binderType != null)
{
ParameterExpression param = Expression.Parameter(typeof(InvokeMemberBinder), "o");
return Expression.Lambda<Func<InvokeMemberBinder, IList<Type>>>(
Expression.TypeAs(
Expression.Field(
Expression.TypeAs(param, binderType), "typeArguments"),
typeof(IList<Type>)), param).Compile();
}
}
else
{
var inter = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder");
if (inter != null)
{
var prop = inter.GetProperty("TypeArguments");
if (!prop.CanRead)
return null;
var objParm = Expression.Parameter(typeof(InvokeMemberBinder), "o");
return Expression.Lambda<Func<InvokeMemberBinder, IList<Type>>>(
Expression.TypeAs(
Expression.Property(
Expression.TypeAs(objParm, inter),
prop.Name),
typeof(IList<Type>)), objParm).Compile();
}
}
return null;
}
/// <summary>Extension method allowing to easyly extract generic type arguments from <see cref="InvokeMemberBinder"/>.</summary>
/// <param name="binder">Binder from which get type arguments.</param>
/// <returns>List of types passed as generic parameters.</returns>
public static IList<Type> GetGenericTypeArguments(this InvokeMemberBinder binder)
{
// First try to use delegate if exist
if (_frameworkTypeArgumentsGetter != null)
return _frameworkTypeArgumentsGetter(binder);
if (_isMono)
{
// In mono this is trivial.
// First we get field info.
var field = binder.GetType().GetField("typeArguments", BindingFlags.Instance |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
// If this was a success get and return it's value
if (field != null)
return field.GetValue(binder) as IList<Type>;
}
else
{
// In this case, we need more aerobic :D
// First, get the interface
var inter = binder.GetType().GetInterface("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder");
if (inter != null)
{
// Now get property.
var prop = inter.GetProperty("TypeArguments");
// If we have a property, return it's value
if (prop != null)
return prop.GetValue(binder, null) as IList<Type>;
}
}
// Sadly return null if failed.
return null;
}
}
Have fun. By the way Impromptu is cool, but I can't use it.
The open source framework Dynamitey can call properties that internal/protected/private using the DLR and thus works with Silverlight. But it get's a little tricky with interface explicit members as you have to use the actual full name of the member on the the type, rather than the interface member name. So you can do:
var typeArgs = Dynamic.InvokeGet(binder, "Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder.TypeArguments")
as IList<Type>;
This question already has answers here:
Get a generic method without using GetMethods
(10 answers)
Closed 9 years ago.
I'm trying to retrieve MethodInfo for Where method of Enumerable type:
typeof (Enumerable).GetMethod("Where", new Type[] {
typeof(IEnumerable<>),
typeof(Func<,>)
})
but get null. What am I doing wrong?
That previous answer works for some cases, however:
It doesn't handle nested generic types, such as a parameter type of Action<IEnumerable<T>>. It will treat all Action<> as matches, for example, string.Concat(IEnumerable<string>) and string.Concat<T>(IEnumerable<T>) will both match if searching for "Concat" with type IEnumerable<> on the string type. What is really desirable is handling nested generic types recursively, while treating all generic parameters as matching each other regardless of name while NOT matching concrete types.
It returns the first method matched rather than throwing an exception if the result is ambiguous, like type.GetMethod() does. So, you might get the method you wanted if you're lucky, or you might not.
Sometimes it will be necessary to specify BindingFlags in order to avoid ambiguity, such as when a derived class method 'hides' a base class method. You normally want to find base class methods, but not in a specialized case where you know the method you're looking for is in the derived class. Or, you might know you're looking for a static vs instance method, public vs private, etc. and don't want to match if it's not exact.
It doesn't address another major fault with type.GetMethods(), in that it also doesn't search base interfaces for methods when looking for a method on an interface type. OK, maybe that's being picky, but it's another major flaw in GetMethods() that has been a problem for me.
Calling type.GetMethods() is inefficient, type.GetMember(name, MemberTypes.Method, ...) will return only methods with a matching name instead of ALL methods in the type.
As a final nit-pick, the name GetGenericMethod() could be misleading, since you might be trying to find a non-generic method that happens to have a type parameter somewhere in a parameter type due to a generic declaring type.
Here's a version that addresses all of those things, and can be used as a general-purpose replacement for the flawed GetMethod(). Note that two extension methods are provided, one with BindingFlags and one without (for convenience).
/// <summary>
/// Search for a method by name and parameter types.
/// Unlike GetMethod(), does 'loose' matching on generic
/// parameter types, and searches base interfaces.
/// </summary>
/// <exception cref="AmbiguousMatchException"/>
public static MethodInfo GetMethodExt( this Type thisType,
string name,
params Type[] parameterTypes)
{
return GetMethodExt(thisType,
name,
BindingFlags.Instance
| BindingFlags.Static
| BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.FlattenHierarchy,
parameterTypes);
}
/// <summary>
/// Search for a method by name, parameter types, and binding flags.
/// Unlike GetMethod(), does 'loose' matching on generic
/// parameter types, and searches base interfaces.
/// </summary>
/// <exception cref="AmbiguousMatchException"/>
public static MethodInfo GetMethodExt( this Type thisType,
string name,
BindingFlags bindingFlags,
params Type[] parameterTypes)
{
MethodInfo matchingMethod = null;
// Check all methods with the specified name, including in base classes
GetMethodExt(ref matchingMethod, thisType, name, bindingFlags, parameterTypes);
// If we're searching an interface, we have to manually search base interfaces
if (matchingMethod == null && thisType.IsInterface)
{
foreach (Type interfaceType in thisType.GetInterfaces())
GetMethodExt(ref matchingMethod,
interfaceType,
name,
bindingFlags,
parameterTypes);
}
return matchingMethod;
}
private static void GetMethodExt( ref MethodInfo matchingMethod,
Type type,
string name,
BindingFlags bindingFlags,
params Type[] parameterTypes)
{
// Check all methods with the specified name, including in base classes
foreach (MethodInfo methodInfo in type.GetMember(name,
MemberTypes.Method,
bindingFlags))
{
// Check that the parameter counts and types match,
// with 'loose' matching on generic parameters
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
if (parameterInfos.Length == parameterTypes.Length)
{
int i = 0;
for (; i < parameterInfos.Length; ++i)
{
if (!parameterInfos[i].ParameterType
.IsSimilarType(parameterTypes[i]))
break;
}
if (i == parameterInfos.Length)
{
if (matchingMethod == null)
matchingMethod = methodInfo;
else
throw new AmbiguousMatchException(
"More than one matching method found!");
}
}
}
}
/// <summary>
/// Special type used to match any generic parameter type in GetMethodExt().
/// </summary>
public class T
{ }
/// <summary>
/// Determines if the two types are either identical, or are both generic
/// parameters or generic types with generic parameters in the same
/// locations (generic parameters match any other generic paramter,
/// but NOT concrete types).
/// </summary>
private static bool IsSimilarType(this Type thisType, Type type)
{
// Ignore any 'ref' types
if (thisType.IsByRef)
thisType = thisType.GetElementType();
if (type.IsByRef)
type = type.GetElementType();
// Handle array types
if (thisType.IsArray && type.IsArray)
return thisType.GetElementType().IsSimilarType(type.GetElementType());
// If the types are identical, or they're both generic parameters
// or the special 'T' type, treat as a match
if (thisType == type || ((thisType.IsGenericParameter || thisType == typeof(T))
&& (type.IsGenericParameter || type == typeof(T))))
return true;
// Handle any generic arguments
if (thisType.IsGenericType && type.IsGenericType)
{
Type[] thisArguments = thisType.GetGenericArguments();
Type[] arguments = type.GetGenericArguments();
if (thisArguments.Length == arguments.Length)
{
for (int i = 0; i < thisArguments.Length; ++i)
{
if (!thisArguments[i].IsSimilarType(arguments[i]))
return false;
}
return true;
}
}
return false;
}
Note that the IsSimilarType(Type) extension method can be made public and might be useful on its own. I know, the name isn't great - you're welcome to come up with a better one, but it might get really long to explain what it does. Also, I added yet another improvement by checking for 'ref' and array types (refs are ignored for matching, but arrays dimensions must match).
So, that's how Microsoft should have done it. It's really not that hard.
Yeah, I know, you can shorten some of that logic using Linq, but I'm not a huge fan of Linq in low-level routines like this, and also not unless the Linq is about as easy to follow as the original code, which is often NOT the case, IMO.
If you love Linq, and you must, you can replace the inner-most part of IsSimilarType() with this (turns 8 lines into 1):
if (thisArguments.Length == arguments.Length)
return !thisArguments.Where((t, i) => !t.IsSimilarType(arguments[i])).Any();
One last thing: If you're looking for a generic method with a generic parameter, such as Method<T>(T, T[]), you'll have to find a Type which is a generic parameter (IsGenericParameter == true) to pass in for the parameter type (any one will do, because of the 'wildcard' matching). However, you can't just do new Type() - you have to find a real one (or build one with TypeBuilder). To make this easier, I added the public class T declaration, and added logic to IsSimilarType() to check for it and match any generic parameter. If you need a T[], just use T.MakeArrayType(1).
Unfortunately, generics are not well-supported in .NET Reflection. In this particular case, you'll need to call GetMethods and then filter the result set for the method you're looking for. An extension method like the following should do the trick.
public static class TypeExtensions
{
private class SimpleTypeComparer : IEqualityComparer<Type>
{
public bool Equals(Type x, Type y)
{
return x.Assembly == y.Assembly &&
x.Namespace == y.Namespace &&
x.Name == y.Name;
}
public int GetHashCode(Type obj)
{
throw new NotImplementedException();
}
}
public static MethodInfo GetGenericMethod(this Type type, string name, Type[] parameterTypes)
{
var methods = type.GetMethods();
foreach (var method in methods.Where(m => m.Name == name))
{
var methodParameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
if (methodParameterTypes.SequenceEqual(parameterTypes, new SimpleTypeComparer()))
{
return method;
}
}
return null;
}
}
With this in hand the following code will work:
typeof(Enumerable).GetGenericMethod("Where", new Type[] { typeof(IEnumerable<>), typeof(Func<,>) });