diff --git a/tracer/src/Datadog.Trace/DuckTyping/DuckAttribute.cs b/tracer/src/Datadog.Trace/DuckTyping/DuckAttribute.cs index e8faaa93f77a..1eba3f586de6 100644 --- a/tracer/src/Datadog.Trace/DuckTyping/DuckAttribute.cs +++ b/tracer/src/Datadog.Trace/DuckTyping/DuckAttribute.cs @@ -15,14 +15,21 @@ namespace Datadog.Trace.DuckTyping internal enum DuckKind { /// - /// Property + /// The target member is a Property /// Property, /// - /// Field + /// The target member is a Field /// - Field + Field, + + /// + /// The target member could be a Property or a Field. + /// Both members will be checked for, the first matching member will be used. + /// Property members are checked for first. + /// + PropertyOrField } /// diff --git a/tracer/src/Datadog.Trace/DuckTyping/DuckPropertyOrFieldAttribute.cs b/tracer/src/Datadog.Trace/DuckTyping/DuckPropertyOrFieldAttribute.cs new file mode 100644 index 000000000000..8732c45ac2cb --- /dev/null +++ b/tracer/src/Datadog.Trace/DuckTyping/DuckPropertyOrFieldAttribute.cs @@ -0,0 +1,21 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable +namespace Datadog.Trace.DuckTyping; + +/// +/// Duck attribute where the underlying member could be a field or a property +/// +internal class DuckPropertyOrFieldAttribute : DuckAttribute +{ + /// + /// Initializes a new instance of the class. + /// + public DuckPropertyOrFieldAttribute() + { + Kind = DuckKind.PropertyOrField; + } +} diff --git a/tracer/src/Datadog.Trace/DuckTyping/DuckType.cs b/tracer/src/Datadog.Trace/DuckTyping/DuckType.cs index 963f54772280..e4877e0a2b8b 100644 --- a/tracer/src/Datadog.Trace/DuckTyping/DuckType.cs +++ b/tracer/src/Datadog.Trace/DuckTyping/DuckType.cs @@ -661,9 +661,15 @@ private static void CreateProperties(TypeBuilder? proxyTypeBuilder, Type proxyDe switch (duckAttribute.Kind) { case DuckKind.Property: + case DuckKind.PropertyOrField: PropertyInfo? targetProperty = GetTargetPropertyOrIndex(targetType, duckAttribute.Name, duckAttribute.BindingFlags, proxyProperty); if (targetProperty is null) { + if (duckAttribute.Kind == DuckKind.PropertyOrField) + { + goto case DuckKind.Field; + } + if (proxyProperty.CanRead && proxyProperty.GetMethod is not null) { var getMethod = proxyProperty.GetMethod; @@ -940,9 +946,15 @@ private static void CreatePropertiesFromStruct(TypeBuilder? proxyTypeBuilder, Ty switch (duckAttribute.Kind) { case DuckKind.Property: + case DuckKind.PropertyOrField: PropertyInfo? targetProperty = GetTargetProperty(targetType, duckAttribute.Name, duckAttribute.BindingFlags); if (targetProperty is null) { + if (duckAttribute.Kind == DuckKind.PropertyOrField) + { + goto case DuckKind.Field; + } + DuckTypePropertyOrFieldNotFoundException.Throw(proxyFieldInfo.Name, duckAttribute.Name, targetType); continue; } diff --git a/tracer/test/Datadog.Trace.DuckTyping.Tests/PropertyOrFieldTests.cs b/tracer/test/Datadog.Trace.DuckTyping.Tests/PropertyOrFieldTests.cs new file mode 100644 index 000000000000..380cb718640f --- /dev/null +++ b/tracer/test/Datadog.Trace.DuckTyping.Tests/PropertyOrFieldTests.cs @@ -0,0 +1,74 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +using FluentAssertions; +using Xunit; + +namespace Datadog.Trace.DuckTyping.Tests; + +public class PropertyOrFieldTests +{ + [Fact] + public void PropertyOrFieldTestUsesPropertyIfAvailable() + { + var target = new TargetWithProperty(); + var proxy = target.DuckCast(); + + proxy.Value.Should().Be(target.Value); + } + + [Fact] + public void PropertyOrFieldTestUsesFieldIfAvailable() + { + var target = new TargetWithField(); + var proxy = target.DuckCast(); + + proxy.Value.Should().Be(target.Value); + } + + [Fact] + public void PropertyOrFieldTestUsesNameFromDuckAttribute() + { + var target = new TargetWithFieldAndProperty(); + var proxy = target.DuckCast(); + + proxy.Value.Should().Be(target.GetValue()); + } + + [DuckCopy] + public struct Proxy + { + [DuckPropertyOrField] + public int Value; + } + + [DuckCopy] + public struct ProxyWithName + { + [DuckPropertyOrField(Name = "value")] + public int Value; + } + + public class TargetWithProperty + { + public int Value { get; } = 1; + } + + public class TargetWithField + { +#pragma warning disable SA1401 // should be private + public int Value = 1; +#pragma warning restore SA1401 + } + + public class TargetWithFieldAndProperty + { + private int value = 3; + + public int Value { get; } = 4; + + public int GetValue() => value; + } +}