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");