Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Additional methods to aid in reflection of generic types #14061

Closed
HaloFour opened this issue Feb 5, 2015 · 20 comments
Closed

Proposal: Additional methods to aid in reflection of generic types #14061

HaloFour opened this issue Feb 5, 2015 · 20 comments
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Reflection
Milestone

Comments

@HaloFour
Copy link

HaloFour commented Feb 5, 2015

I don't know if this is the appropriate place to make such a suggestion.

Problem

I've always found it a bit annoying that the reflection APIs in .NET haven't been improved much since the advent of generics and when working with generic types or methods that considerable additional boilerplate needs to be written in order to correctly work with these types.

Solution

In the past I've implemented helper extension methods which significantly simplify reflection over generic types and methods. These extension methods largely work on top of the public surface of the System.Type class, although a few dealing with generic methods did resort to reflection in order to call internal methods to better detect and deal with ambiguities. I think that it would be very convenient to have these methods (or something like them) considered for addition into the core framework.

I am willing and able to share my extension methods and could start a branch if there is traction to this idea.

FindGenericInterfaces

Identical to Type.FindInterfaces except that it will match on open generic interface types.

public delegate bool GenericTypeFilter(Type type, Type[] genericArguments, object filterCriteria);

public static Type[] FindGenericInterfaces(this Type type, Type genericInterfaceType);
public static Type[] FindGenericInterfaces(this Type type, Type genericInterfaceType, GenericTypeFilter filter, object filterCriteria);

Type[] interfaces = typeof(int[]).FindGenericInterfaces(typeof(IEnumerable<>), (type, types, criteria) => types[0] == typeof(int), null);

ImplementsInterface

Returns true if the type implements the specified interface. The interface can either be an open generic type or a closed constructed type. If the case of the former the overloads can be used to return the generic type arguments. For those overloads that accept a specific count of out parameters the generic type arguments must match the count of the out Type arguments. For the overload that accepts a Type[] the number of generic type arguments must match the length of the array. The overload that accepts an out Type[] will match any number of generic type arguments.

I also have IsSubclassOf and Is extension methods that function in almost the same manner except that the first matches if the type is a subclass of the specified type and the second matches if the type is a subclass of, implements or is of the specified type.

public static bool ImplementsInterface(this Type type, Type interfaceType);
public static bool ImplementsInterface(this Type type, Type interfaceType, out Type genericArgument);
public static bool ImplementsInterface(this Type type, Type interfaceType, out Type genericArgument1, out Type genericArgument2);
public static bool ImplementsInterface(this Type type, Type interfaceType, out Type genericArgument1, out Type genericArgument2, out Type genericArgument3);
public static bool ImplementsInterface(this Type type, Type interfaceType, Type[] genericArguments);
public static bool ImplementsInterface(this Type type, Type interfaceType, out Type[] genericArguments);

Type elementType, keyType;
if (typeof(int[]).ImplementsInterface(typeof(IEnumerable<int>)) { ... }
if (typeof(int[]).ImplementsInterface(typeof(IEnumerable<>), out elementType)) { ... }
if (typeof(Dictionary<string, int>).ImplementsInterface(typeof(IDictionary<>), out keyType, out elementType)) { ... }
Type[] arguments = new Type[2];
if (typeof(Dictionary<string, int>).ImplementsInterface(typeof(IDictionary<>), arguments)) { ... }
arguments = null;
if (typeof(Dictionary<string, int>).ImplementsInterface(typeof(IDictionary<>), out arguments)) { ... }

GetGenericMethod

Separated into proposal Add GetGenericMethod method to simplify finding generic methods via reflection.

Returns the generic method with the same number of specified generic arguments. The generic arguments are checked for compatibility with the generic type constraints. If a parameter type list is also specified then the parameter types must be acceptable for the given generic type arguments.

In order to properly handle matches and ambiguity resolution I did have to break out reflection in order to invoke the internal members RuntimeType.FilterApplyMethodBase, DefaultBinder.FindMostDerivedNewSlotMeth and DefaultBinder.CompareMethodSigAndName. A built-in implementation would not require to rely on any reflection.

public static MethodInfo GetGenericMethod(this Type type, string name, Type[] genericArgs);
public static MethodInfo GetGenericMethod(this Type type, string name, Type[] genericArgs, BindingFlags bindingAttrs);
public static MethodInfo GetGenericMethod(this Type type, string name, Type[] genericArgs, Type[] types);
public static MethodInfo GetGenericMethod(this Type type, string name, Type[] genericArgs, Type[] types, ParameterModifier[] modifiers);
public static MethodInfo GetGenericMethod(this Type type, string name, Type[] genericArgs, BindingFlags bindingAttrs, Binder binder, Type[] types, ParameterModifier[] modifiers);
public static MethodInfo GetGenericMethod(this Type type, string name, Type[] genericArgs, BindingFlags bindingAttrs, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers);

public class Foo {
    public void Bar(object value) { ... }
    public void Bar<T>(T value) { ... }
}

MethodInfo genericBar = typeof(Foo).GetGenericMethod("Bar", new Type[] { typeof(string) }, new Type[] { typeof(string) });
@HaloFour
Copy link
Author

HaloFour commented Feb 9, 2015

Here is my current implementation which is fully functional, at least on the full .NET framework up to 4.5.2.

TypeExtensions.cs

@terrajobst terrajobst assigned nguerrera and unassigned terrajobst Sep 8, 2015
@terrajobst
Copy link
Member

@nguerrera, could you take a look whether that's something that would make sense for our reflection library?

@danmoseley
Copy link
Member

@nguerrera any thoughts?

@HaloFour
Copy link
Author

To note the APIs I've described above and my current implementations are simply suggestions. I'd be happy with any implementation that made it easier to work with generic types and methods, preferably reducing most operations such as obtaining a MethodInfo to a specific generic method to a one-liner. For example a week or so ago I was helping a friend manually construct an expression tree using OrderBy against a property resolved by a string and that required finding a correct MethodInfo to Queryable.OrderBy. Currently that requires looping through all of the methods on the type and manually determining candidates based on name, parameter arity, type compatibility, etc. With my helper method implementation that's reduced to the following:

MethodInfo orderByMethod = typeof(Queryable).GetGenericMethod("OrderBy", // name
            new Type[] { typeof(Person), typeof(string) },  // generic type arguments
            BindingFlags.Static | BindingFlags.Public,
            Type.DefaultBinder,
            new Type[] { typeof(IQueryable<Person>), typeof(Expression<Func<Person, string>>) }, // parameter types
            null);

My implementation has also been in use in a large and heavily customized WCF service for the last 5 years

@karelz karelz assigned ghost and unassigned nguerrera Feb 28, 2017
@karelz
Copy link
Member

karelz commented Feb 28, 2017

@atsushikan please review the proposal to see if it is ready.

@ghost
Copy link

ghost commented Mar 1, 2017

@HaloFour - if you'd still like to see this go forward, can you split the issue into one for GetGenericMethod() and one (or two) for the interface related apis? It seems like GetGenericMethod is the one that's has motivation right now.

@HaloFour
Copy link
Author

HaloFour commented Mar 1, 2017

@atsushikan

Sure. Can I reference those issues back to this one or should I flesh them out individually?

@ghost
Copy link

ghost commented Mar 1, 2017

My recommendation would be to narrow this one down to GetGenericMethod only and then open separate issues for the interface related proposals. (Concrete use cases for those would be good too.) I think we'd want to consider these api groups separately.

@karelz
Copy link
Member

karelz commented Mar 1, 2017

@HaloFour please update the top post and ping us when it is ready, we can then move it forward ...

@karelz karelz unassigned ghost Mar 1, 2017
@HaloFour
Copy link
Author

HaloFour commented Mar 1, 2017

@atsushikan @karelz

dotnet/corefx#16567

@karelz
Copy link
Member

karelz commented Mar 1, 2017

@HaloFour thanks! Do you plan to update this issue to track the interface proposal?

@HaloFour
Copy link
Author

HaloFour commented Mar 1, 2017

@karelz

Yes, I can make separate proposals for that. Do you think that FindGenericInterfaces, ImplementsInterface, IsSubclassOf and Is should all be their own proposals or can they be lumped together as one?

@ghost
Copy link

ghost commented Mar 1, 2017 via email

@jnm2
Copy link
Contributor

jnm2 commented May 30, 2017

I can't overstate how much I want this (and more) in the BCL. Reflection code is time-consuming and hard to get right. The BCL does okay for a few limited type system things and then just abandons you when it gets complicated.

Olafski referenced this issue in Olafski/corefx Jun 15, 2017
add initial 2.0 contributor list
@kingces95
Copy link
Contributor

kingces95 commented Aug 1, 2017

If these are all helper APIs, why not distribute them in a non-core nuget package instead of pulling them into core? I'm sure there will be other helper APIs we'd like to add going forward and the bar for adding new helpers to a non-core nuget package is much lower than submission into core.

@jnm2
Copy link
Contributor

jnm2 commented Aug 1, 2017

I get your point, but at the same time having to distribute a helper library dependency just for one type of reflection task is distasteful enough to me that I'd prefer to write it manually.

@kingces95
Copy link
Contributor

Shouldn't the bar for including logic in core be higher than any logic that requires users distribute a helper library?

@HaloFour
Copy link
Author

HaloFour commented Aug 1, 2017

@kingces95

You could say that about the bulk of the BCL as it stands today. In my opinion reflection is a pretty fundamental feature. Having to manually enumerate every single method and reimplement binding rules and logic already baked into the BCL (but internal) just because the method you want happens to be generic doesn't make a lot of sense to me. It's very easy to get wrong.

@kingces95
Copy link
Contributor

To the extent we're exposing complex logic already implemented in the BLC then I'm all for it!

@ghost
Copy link

ghost commented Sep 11, 2018

Cleaning out old issues with no action as part of ownership transfer.

@ghost ghost closed this as completed Sep 11, 2018
@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 3.0 milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Jan 7, 2021
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-needs-work API needs work before it is approved, it is NOT ready for implementation area-System.Reflection
Projects
None yet
Development

No branches or pull requests

8 participants