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;
+ }
+}