@@ -203,19 +203,24 @@ private static void AgentConvertSpan<TInner>(TInner activity, Span span)
203203 }
204204
205205 // extract any ActivityLinks and ActivityEvents
206- ExtractActivityLinks < TInner > ( span , activity5 ) ;
207- ExtractActivityEvents < TInner > ( span , activity5 ) ;
206+ if ( activity5 is not null )
207+ {
208+ ExtractActivityLinks ( span , activity5 ) ;
209+ ExtractActivityEvents ( span , activity5 ) ;
210+ }
208211 }
209212
210- private static void ExtractActivityLinks < TInner > ( Span span , IActivity5 ? activity5 )
211- where TInner : IActivity
213+ private static void ExtractActivityLinks < TInner > ( Span span , TInner activity5 )
214+ where TInner : IActivity5
212215 {
213- if ( activity5 is null )
216+ // The underlying storage when there are no link is an empty array, so bail out in this scenario,
217+ // to avoid the allocations from boxing the enumerator
218+ if ( ! activity5 . HasLinks ( ) )
214219 {
215220 return ;
216221 }
217222
218- foreach ( var link in ( activity5 . Links ) )
223+ foreach ( var link in activity5 . Links )
219224 {
220225 if ( ! link . TryDuckCast < IActivityLink > ( out var duckLink )
221226 || duckLink . Context . TraceId . TraceId is null
@@ -264,7 +269,7 @@ private static void ExtractActivityLinks<TInner>(Span span, IActivity5? activity
264269 spanContext . LastParentId = traceState . LastParent ;
265270 spanContext . PropagatedTags = traceTags ;
266271
267- List < KeyValuePair < string , string > > attributes = new ( ) ;
272+ List < KeyValuePair < string , string > > ? attributes = null ;
268273 if ( duckLink . Tags is not null )
269274 {
270275 foreach ( var kvp in duckLink . Tags )
@@ -279,13 +284,15 @@ private static void ExtractActivityLinks<TInner>(Span span, IActivity5? activity
279284 {
280285 if ( item ? . ToString ( ) is { } value )
281286 {
287+ attributes ??= new ( ) ;
282288 attributes . Add ( new ( $ "{ kvp . Key } .{ index } ", value ) ) ;
283289 index ++ ;
284290 }
285291 }
286292 }
287293 else if ( kvp . Value ? . ToString ( ) is { } kvpValue )
288294 {
295+ attributes ??= new ( ) ;
289296 attributes . Add ( new ( kvp . Key , kvpValue ) ) ;
290297 }
291298 }
@@ -296,10 +303,12 @@ private static void ExtractActivityLinks<TInner>(Span span, IActivity5? activity
296303 }
297304 }
298305
299- private static void ExtractActivityEvents < TInner > ( Span span , IActivity5 ? activity5 )
300- where TInner : IActivity
306+ private static void ExtractActivityEvents < TInner > ( Span span , TInner activity5 )
307+ where TInner : IActivity5
301308 {
302- if ( activity5 is null )
309+ // The underlying storage when there are no link is an empty array, so bail out in this scenario,
310+ // to avoid the allocations from boxing the enumerator
311+ if ( ! activity5 . HasEvents ( ) )
303312 {
304313 return ;
305314 }
@@ -311,11 +320,12 @@ private static void ExtractActivityEvents<TInner>(Span span, IActivity5? activit
311320 continue ;
312321 }
313322
314- var eventAttributes = new List < KeyValuePair < string , object > > ( ) ;
323+ List < KeyValuePair < string , object > > ? eventAttributes = null ;
315324 foreach ( var kvp in duckEvent . Tags )
316325 {
317326 if ( ! string . IsNullOrEmpty ( kvp . Key ) && kvp . Value is not null )
318327 {
328+ eventAttributes ??= new ( ) ;
319329 eventAttributes . Add ( new ( kvp . Key , kvp . Value ) ) ;
320330 }
321331 }
@@ -526,7 +536,7 @@ private static void ExtractExceptionAttributes<TInner>(TInner activity, Span spa
526536 {
527537 // OpenTelemetry stores the exception attributes in Activity.Events
528538 // Activity.Events was only added in .NET 5+, which maps to our IActivity5 & IActivity6
529- if ( activity is not IActivity5 activity5 )
539+ if ( activity is not IActivity5 activity5 || ! activity5 . HasEvents ( ) )
530540 {
531541 return ;
532542 }
0 commit comments