From 2d68cdad2b8406ba1f986ad304b0d24372deb86a Mon Sep 17 00:00:00 2001 From: Mitchell Skaggs Date: Sun, 5 Jan 2025 08:59:57 -0600 Subject: [PATCH] Allow overridden properties that inherit attributes Previously, the property in the base class and the property in the subclass would both be returned, causing us to complain about duplicate arguments/options that are actually the same. Closes https://github.com/natemcmaster/CommandLineUtils/issues/555 --- .../Internal/ReflectionHelper.cs | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/CommandLineUtils/Internal/ReflectionHelper.cs b/src/CommandLineUtils/Internal/ReflectionHelper.cs index 07b6daec..ce46027a 100644 --- a/src/CommandLineUtils/Internal/ReflectionHelper.cs +++ b/src/CommandLineUtils/Internal/ReflectionHelper.cs @@ -14,7 +14,10 @@ namespace McMaster.Extensions.CommandLineUtils { internal static class ReflectionHelper { - private const BindingFlags DeclaredOnlyLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly; + private const BindingFlags InheritedLookup = BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.Static; + + private const BindingFlags DeclaredOnlyLookup = InheritedLookup | BindingFlags.DeclaredOnly; public static SetPropertyDelegate GetPropertySetter(PropertyInfo prop) { @@ -158,19 +161,50 @@ public static bool IsSpecialTupleType(Type type, [NotNullWhen(true)] out Type? w return result; } + private class MethodMetadataEquality : IEqualityComparer + { + public bool Equals(MethodInfo? x, MethodInfo? y) + { + if (x == null && y == null) + { + return true; + } + + return x != null && y != null && x.HasSameMetadataDefinitionAs(y); + } + + public int GetHashCode(MethodInfo obj) + { + return obj.HasMetadataToken() ? obj.GetMetadataToken().GetHashCode() : 0; + } + } + private static IEnumerable GetAllMembers(Type type) { - while (type != null) + // Keep track of the base definitions of property getters we see so we can skip ones we've seen already + var baseGetters = new HashSet(new MethodMetadataEquality()); + + var currentType = type; + while (currentType != null) { - var members = type.GetMembers(DeclaredOnlyLookup); + var members = currentType.GetMembers(InheritedLookup); foreach (var member in members) { + if (member is PropertyInfo property) + { + var getter = property.GetGetMethod(true)?.GetBaseDefinition(); + + // If we have a getter, try to add it to our set. If it _wasn't_ a new element, don't yield it. + if (getter != null && !baseGetters.Add(getter)) + { + continue; + } + } + yield return member; } -#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. - type = type.BaseType; -#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type. + currentType = currentType.BaseType; } } }