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

Reflection_Engine: SortExtensionMethods improved to return the most relevant method #2327

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 66 additions & 5 deletions Reflection_Engine/Modify/SortExtensionMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,72 @@ public static partial class Modify
[Output("metods", "Sorted methods")]
public static List<MethodInfo> SortExtensionMethods(this IEnumerable<MethodInfo> methods, Type type)
{
//TODO: Sort methods based on closeness to the type, not just exact vs non exact.
//Example A : B and B : C
//Then if the type is A and list of methods contain one method with first parameter matching each type, then the list should be
//Sorted so that the method with A comes first followed by B and lastly C.
return methods.OrderBy(x => x.GetParameters().FirstOrDefault()?.ParameterType == type ? 0 : 1).ToList();
List<List<Type>> hierarchy = type.InheritanceHierarchy();
IEnumerable<int> levels = methods.Select(x => hierarchy.InheritanceLevel(x.GetParameters()[0].ParameterType));
return methods.Zip(levels, (m, l) => new { m, l }).Where(x => x.l != -1).OrderBy(x => x.l).Select(x => x.m).ToList();
Comment on lines +45 to +47
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should look into what the Weighting system in the BHoM_UI does since this is a very similar problem:
https://github.com/BHoM/BHoM_UI/blob/631e784772f456dd881492262d7f10c5c2fb7322/UI_Engine/Query/Weight.cs#L190

I am not convinced it needs to be as complex as that though. Speed is quite an important factor here and I think we should cut everything that is not that relevant. I wonder if this wouldn't be enough:

  • exact type match : comes first
  • type is assignable from: comes second
  • type not assignable from: comes last

I am not aware of the details of the situation you are trying to solve by this seems like a good compromise to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proposed solution works as follows:

  • exact type match : comes first
  • type is assignable from, closer to the top of inheritance tree: comes second
  • type is assignable from, further from the top of inheritance tree: comes thirs
  • type not assignable from: gets filtered out (it will throw an error at some point anyways)

The reason for such complex approach can be explained on the Transform method developed in #2321 (WIP) for BH.oM.Physical.Elements.Beam:

  • there will be an interface method ITransform taking BH.oM.Dimensional.IElement1D that will call Transform
  • there will be a general Transform method taking BH.oM.Dimensional.IElement1D that will only transform the driving curve
  • there will be a more specific Transform method taking BH.oM.Physical.Elements.IFramingElement that besides driving curve transformation will change the rotation property
  • there will be no type-specific Transfrom method for BH.oM.Physical.Elements.Beam

It is important to call the method taking BH.oM.Physical.Elements.IFramingElement not to lose the rotation part. Naturally, one could add Transform method for each type implementing BH.oM.Physical.Elements.IFramingElement instead, but this sounds like quite a bit of boilerplate, plus what if one adds another type being not aware that this method needs to be added?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it comes to performance, I have tested this and it runs in <10ms, plus it it is ran only once per type/method name combination (then stored in a public field). So I think we should be good with that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean. I am glad you are keeping those methods private though as their result is not exactly matching what a human would produce. Take for example the output of InheritanceHierarchy for a structural bar:

image

For example, it doesn't really make sense for BHoMObject to have such a big priority compared to other interfaces. Same thing for IBHoMObject being prioritised over IElement. Not the fault of you algorithm though as interfaces in the BHoM are mostly used as a tag and no so much as a strict hierarchy. On the other hand, trying to sort base types and interfaces on the same list is always going to be a bit iffy.

That's why I was suggesting a simpler 3-levels system in order to avoid a complex sorting system that has many imperfect cases. But, again, I understand why you needed something more.

I would like to have @IsakNaslundBh final opinion as well on this one before agreeing to merge.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fully agree with your comment @adecler, the system is not perfect. However, I approached it based on the assumption that an approximate solution is better than quasi-random:

  • on master, the exact type goes first, all others are random
  • on this branch, the exact type goes first, then the order is reflecting the inheritance tree, which has the faults listed by you

On top of that, from the problem-specific point of view it should be fine to have BHoMObject high in the hierarchy because it will be simply ignored due to the lack of extension methods. This does not mean the method is any more correct than it actually is, but it should not fail in the discussed application of RunExtensionMethod 🙈

Happy to hear @IsakNaslundBh's opinion too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll approve this so you're not necessarily stuck waiting for @IsakNaslundBh if this needs merging. Personally, I am ok with this being merged.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry. Been struggeling a bit with time!

Just reading through the comments here, I think this looks like an improvement! Agree that the speed is not that big of an issue for the reasons @pawelbaran gave, rather have this behave as good as it can. Ideally, we would have this do the exact same thing that C# itself does when it comes to method overloading/choice, which I guess this is one step closer to doing compared to where it was before.

Have not tested this myself, but given that @adecler has, you do not have to wait for me. I approve on the concept 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, we would have this do the exact same thing that C# itself does when it comes to method overloading/choice, which I guess this is one step closer to doing compared to where it was before.

I am afraid we do not want that @IsakNaslundBh, the compiler would throw an error about method ambiguity when pushed to choose between an interface method and the parent type method:

image

So there might be no perfect solution I am afraid.

}


/***************************************************/
/**** Private Methods ****/
/***************************************************/

private static List<List<Type>> InheritanceHierarchy(this Type type)
{
List<Type> typeAncestry = new List<Type>();
Type ancestor = type;
while (ancestor != null && ancestor != typeof(object))
{
typeAncestry.Add(ancestor);
ancestor = ancestor.BaseType;
}

List<List<Type>> result = new List<List<Type>>();

for (int i = typeAncestry.Count - 1; i >= 0; i--)
{
Type child = typeAncestry[i];
result.Insert(0, new List<Type> { child });

IEnumerable<Type> alreadyMapped = result.SelectMany(x => x);
List<List<Type>> interfaces = child.InterfaceHierarchy();
for (int j = 0; j < interfaces.Count; j++)
{
IEnumerable<Type> notMapped = interfaces[j].Except(alreadyMapped);
if (notMapped.Any())
{
if (j + 1 < result.Count)
result[j + 1].AddRange(notMapped);
else
result.Add(notMapped.ToList());
}
else
break;
}
}

return result;
}

/***************************************************/

private static List<List<Type>> InterfaceHierarchy(this Type type)
{
Type[] interfaces = type.GetInterfaces();
return interfaces.GroupBy(x => interfaces.Count(y => x.IsAssignableFrom(y))).OrderBy(x => x.Key).Select(x => x.ToList()).ToList();
}

/***************************************************/

private static int InheritanceLevel(this List<List<Type>> hierarchy, Type type)
{
for (int i = 0; i < hierarchy.Count; i++)
{
if (hierarchy[i].Contains(type))
return i;
}

return -1;
}

/***************************************************/
Expand Down