diff --git a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml index d728f2b101..adf8dfa26c 100644 --- a/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml +++ b/src/Hl7.Fhir.Base/CompatibilitySuppressions.xml @@ -778,6 +778,13 @@ lib/net8.0/Hl7.Fhir.Base.dll true + + CP0002 + M:Hl7.Fhir.Utility.ReflectionHelper.FindPublicProperties(System.Type) + lib/net8.0/Hl7.Fhir.Base.dll + lib/net8.0/Hl7.Fhir.Base.dll + true + CP0002 M:Hl7.Fhir.Utility.ReflectionHelper.IsTypedCollection(System.Type) diff --git a/src/Hl7.Fhir.Base/Introspection/ClassMapping.cs b/src/Hl7.Fhir.Base/Introspection/ClassMapping.cs index a840c43152..bb18ed7a4c 100644 --- a/src/Hl7.Fhir.Base/Introspection/ClassMapping.cs +++ b/src/Hl7.Fhir.Base/Introspection/ClassMapping.cs @@ -324,14 +324,36 @@ private PropertyMappingCollection inspectProperties() IEnumerable map() { - foreach (var property in ReflectionHelper.FindPublicProperties(NativeType)) + var properties = selectNearestProperties(ReflectionHelper.FindPublicProperties(NativeType)); + + foreach (var property in properties) { if (!PropertyMapping.TryCreate(property, out var propMapping, this, Release)) continue; - yield return propMapping!; + yield return propMapping; } } } + /// + /// When redefining a property using `new` in a subclass, the property will be present multiple times in the + /// list of properties. This method will select the property from the "closest" declaring type in the + /// inheritance hierarchy to the type of the class mapping. + /// + private static IEnumerable selectNearestProperties(IReadOnlyCollection properties) + { + var hierarchyComparer = Comparer.Create(compareInheritance); + var ordered = properties.OrderBy(p => p, hierarchyComparer); + return ordered.GroupBy(p => p.Name).Select(g => g.First()).ToList(); + } + + private static int compareInheritance(PropertyInfo x, PropertyInfo y) + { + if (x.DeclaringType == y.DeclaringType) return 0; + if (x.DeclaringType!.IsAssignableFrom(y.DeclaringType)) return 1; + if (y.DeclaringType!.IsAssignableFrom(x.DeclaringType)) return -1; + return 0; + } + private static string collectTypeName(FhirTypeAttribute attr, Type type) { var name = attr.Name; diff --git a/src/Hl7.Fhir.Base/Introspection/PropertyMappingCollection.cs b/src/Hl7.Fhir.Base/Introspection/PropertyMappingCollection.cs index 6312a44026..abcd0eb8b3 100644 --- a/src/Hl7.Fhir.Base/Introspection/PropertyMappingCollection.cs +++ b/src/Hl7.Fhir.Base/Introspection/PropertyMappingCollection.cs @@ -24,10 +24,11 @@ public PropertyMappingCollection(IEnumerable mappings) foreach (var mapping in mappings) { var propKey = mapping.Name; - if (byName.ContainsKey(propKey)) - throw Error.InvalidOperation($"Class has multiple properties that are named '{propKey}'. The property name must be unique."); - - byName[propKey] = mapping; + if (!byName.TryAdd(propKey, mapping)) + { + throw Error.InvalidOperation( + $"Class has multiple properties that are named '{propKey}'. The property name must be unique."); + } } ByName = byName; diff --git a/src/Hl7.Fhir.Base/Model/Code.cs b/src/Hl7.Fhir.Base/Model/Code.cs index 0fb406b80e..27557a372b 100644 --- a/src/Hl7.Fhir.Base/Model/Code.cs +++ b/src/Hl7.Fhir.Base/Model/Code.cs @@ -41,7 +41,7 @@ public partial class Code /// /// Creates a from an instance of a . /// - public ElementModel.Types.Code ToSystemCode() => new(system: null, code: Value, display: null, version: null); + public virtual ElementModel.Types.Code ToSystemCode() => new(system: null, code: Value, display: null, version: null); /// /// Checks whether the given literal is correctly formatted. diff --git a/src/Hl7.Fhir.Base/Model/CodeOfT.cs b/src/Hl7.Fhir.Base/Model/CodeOfT.cs index 5dd8b2d3dc..ec0040ab06 100644 --- a/src/Hl7.Fhir.Base/Model/CodeOfT.cs +++ b/src/Hl7.Fhir.Base/Model/CodeOfT.cs @@ -42,11 +42,15 @@ POSSIBILITY OF SUCH DAMAGE. namespace Hl7.Fhir.Model { + /// + /// A that has a limited set of values and which can therefore + /// be represented as an enumerated type. + /// [Serializable] [FhirType("codeOfT")] [DataContract] [System.Diagnostics.DebuggerDisplay(@"\{Value={Value}}")] - public class Code : PrimitiveType, INullableValue, ISystemAndCode where T : struct, Enum + public class Code : Code, INullableValue, ISystemAndCode where T : struct, Enum { static Code() { @@ -66,7 +70,7 @@ public Code(T? value) // Primitive value of element [FhirElement("value", IsPrimitiveValue = true, XmlSerialization = XmlRepresentation.XmlAttr, InSummary = true, Order = 30)] [DataMember] - public T? Value + new public T? Value { get => TryParseObjectValue(out var value) ? value @@ -94,7 +98,11 @@ internal bool TryParseObjectValue(out T? value) string ISystemAndCode.Code => Value?.GetLiteral(); - public S.Code ToSystemCode() => new(Value?.GetSystem(), Value?.GetLiteral(), display: null, version: null); + public override S.Code ToSystemCode() => + new(Value?.GetSystem(), + Value?.GetLiteral() ?? throw new InvalidOperationException("Code must have a value in order to be useable to construct a System.Code."), + display: null, + version: null); public override IEnumerable Validate(ValidationContext validationContext) { diff --git a/src/Hl7.Fhir.Base/Model/ICoded.cs b/src/Hl7.Fhir.Base/Model/ICoded.cs index f0b220bed5..afbf0f3587 100644 --- a/src/Hl7.Fhir.Base/Model/ICoded.cs +++ b/src/Hl7.Fhir.Base/Model/ICoded.cs @@ -50,8 +50,8 @@ public static class CodedExtensions public static IEnumerable ToCodings(this DataType? dt) => dt switch { null => Enumerable.Empty(), - Code co => new[] { new Coding(null, co.Value) }, ISystemAndCode sac => new[] { new Coding(sac.System, sac.Code) }, + Code co => new[] { new Coding(null, co.Value) }, Coding cd => new[] { cd }, CodeableConcept cc => cc.Coding ?? Enumerable.Empty(), Quantity q => new[] { new Coding(q.System, q.Code) }, diff --git a/src/Hl7.Fhir.Base/Utility/ReflectionHelper.cs b/src/Hl7.Fhir.Base/Utility/ReflectionHelper.cs index 883d750163..0cd24603ff 100644 --- a/src/Hl7.Fhir.Base/Utility/ReflectionHelper.cs +++ b/src/Hl7.Fhir.Base/Utility/ReflectionHelper.cs @@ -56,7 +56,7 @@ public static PropertyInfo FindProperty(Type t, string name) => /// /// /// - public static IEnumerable FindPublicProperties(Type t) + public static PropertyInfo[] FindPublicProperties(Type t) { if (t == null) throw Error.ArgumentNull("t");