@@ -25,7 +25,7 @@ static class ClassEmitterExtensions {
25
25
/// Emit the selector fields for the current class. The method will add the fields to the binding context so that
26
26
/// they can be used later.
27
27
/// </summary>
28
- /// <param name="self"></param>
28
+ /// <param name="self">The class emitter. </param>
29
29
/// <param name="bindingContext">The current binding context.</param>
30
30
/// <param name="classBlock">The current class block.</param>
31
31
public static void EmitSelectorFields ( this IClassEmitter self , in BindingContext bindingContext , TabbedWriter < StringWriter > classBlock )
@@ -257,7 +257,7 @@ public static void EmitMethods (this IClassEmitter self, in BindingContext conte
257
257
/// Emit the code for all the field properties in the class. The code will add any necessary backing fields and
258
258
/// will return all properties that are notifications.
259
259
/// </summary>
260
- /// <param name="self"></param>
260
+ /// <param name="self">The class emitter. </param>
261
261
/// <param name="className">The current class name.</param>
262
262
/// <param name="properties">All properties of the class, the method will filter those that are fields.</param>
263
263
/// <param name="classBlock">Current class block.</param>
@@ -332,4 +332,169 @@ public static void EmitFields (this IClassEmitter self, string className, in Imm
332
332
}
333
333
notificationProperties = notificationsBuilder . ToImmutable ( ) ;
334
334
}
335
+
336
+ /// <summary>
337
+ /// Emits the code for a given property.
338
+ /// </summary>
339
+ /// <param name="self">The class emitter.</param>
340
+ /// <param name="context">The current binding context.</param>
341
+ /// <param name="property">The property to emit.</param>
342
+ /// <param name="classBlock">The current class block writer.</param>
343
+ /// <param name="uiThreadCheck">An optional UI thread check expression. If not provided, it will be created based on the context.</param>
344
+ public static void EmitProperty ( this IClassEmitter self , in BindingContext context , in Property property ,
345
+ TabbedWriter < StringWriter > classBlock , ExpressionStatementSyntax ? uiThreadCheck = null )
346
+ {
347
+
348
+ // if not passed as an argument, we will create the ui thread check based on the context
349
+ if ( uiThreadCheck is null ) {
350
+ uiThreadCheck = ( context . NeedsThreadChecks )
351
+ ? EnsureUiThread ( context . RootContext . CurrentPlatform )
352
+ : null ;
353
+ }
354
+
355
+ if ( property . IsField )
356
+ // ignore fields
357
+ return ;
358
+ // use the factory to generate all the needed invocations for the current
359
+ var invocations = GetInvocations ( property ) ;
360
+
361
+ // we expect to always at least have a getter
362
+ var getter = property . GetAccessor ( AccessorKind . Getter ) ;
363
+ if ( getter . IsNullOrDefault )
364
+ return ;
365
+
366
+ // add backing variable for the property if it is needed
367
+ if ( property . NeedsBackingField ) {
368
+ classBlock . WriteLine ( ) ;
369
+ classBlock . AppendGeneratedCodeAttribute ( optimizable : true ) ;
370
+ classBlock . WriteLine ( $ "object? { property . BackingField } = null;") ;
371
+ }
372
+
373
+ classBlock . WriteLine ( ) ;
374
+ classBlock . AppendMemberAvailability ( property . SymbolAvailability ) ;
375
+ classBlock . AppendGeneratedCodeAttribute ( optimizable : true ) ;
376
+
377
+ using ( var propertyBlock = classBlock . CreateBlock ( property . ToDeclaration ( ) . ToString ( ) , block : true ) ) {
378
+ // be very verbose with the availability, makes the life easier to the dotnet analyzer
379
+ propertyBlock . AppendMemberAvailability ( getter . SymbolAvailability ) ;
380
+ // if we deal with a delegate, include the attr:
381
+ // [return: DelegateProxy (typeof ({staticBridge}))]
382
+ if ( property . ReturnType . IsDelegate )
383
+ propertyBlock . AppendDelegateProxyReturn ( property . ReturnType ) ;
384
+ using ( var getterBlock = propertyBlock . CreateBlock ( "get" , block : true ) ) {
385
+ if ( uiThreadCheck is not null ) {
386
+ getterBlock . WriteLine ( uiThreadCheck . ToString ( ) ) ;
387
+ getterBlock . WriteLine ( ) ;
388
+ }
389
+ // depending on the property definition, we might need a temp variable to store
390
+ // the return value
391
+ var ( tempVar , tempDeclaration ) = GetReturnValueAuxVariable ( property . ReturnType ) ;
392
+ getterBlock . WriteRaw (
393
+ $@ "{ tempDeclaration }
394
+ if (IsDirectBinding) {{
395
+ { ExpressionStatement ( invocations . Getter . Send ) }
396
+ }} else {{
397
+ { ExpressionStatement ( invocations . Getter . SendSuper ) }
398
+ }}
399
+ { ExpressionStatement ( KeepAlive ( "this" ) ) }
400
+ " ) ;
401
+ if ( property . RequiresDirtyCheck || property . IsWeakDelegate ) {
402
+ getterBlock . WriteLine ( "MarkDirty ();" ) ;
403
+ }
404
+
405
+ if ( property . NeedsBackingField ) {
406
+ getterBlock . WriteLine ( $ "{ property . BackingField } = { tempVar } ;") ;
407
+ }
408
+
409
+ getterBlock . WriteLine ( $ "return { tempVar } ;") ;
410
+ }
411
+
412
+ var setter = property . GetAccessor ( AccessorKind . Setter ) ;
413
+ if ( setter . IsNullOrDefault || invocations . Setter is null )
414
+ // we are done with the current property
415
+ return ;
416
+
417
+ propertyBlock . WriteLine ( ) ; // add space between getter and setter since we have the attrs
418
+ propertyBlock . AppendMemberAvailability ( setter . SymbolAvailability ) ;
419
+ // if we deal with a delegate, include the attr:
420
+ // [param: BlockProxy (typeof ({nativeInvoker}))]
421
+ if ( property . ReturnType . IsDelegate )
422
+ propertyBlock . AppendDelegateParameter ( property . ReturnType ) ;
423
+ using ( var setterBlock = propertyBlock . CreateBlock ( "set" , block : true ) ) {
424
+ if ( uiThreadCheck is not null ) {
425
+ setterBlock . WriteLine ( uiThreadCheck . ToString ( ) ) ;
426
+ setterBlock . WriteLine ( ) ;
427
+ }
428
+ // init the needed temp variables
429
+ setterBlock . Write ( invocations . Setter . Value . Argument . Initializers , verifyTrivia : false ) ;
430
+ setterBlock . Write ( invocations . Setter . Value . Argument . Validations , verifyTrivia : false ) ;
431
+ setterBlock . Write ( invocations . Setter . Value . Argument . PreCallConversion , verifyTrivia : false ) ;
432
+
433
+ // perform the invocation
434
+ setterBlock . WriteRaw (
435
+ $@ "if (IsDirectBinding) {{
436
+ { ExpressionStatement ( invocations . Setter . Value . Send ) }
437
+ }} else {{
438
+ { ExpressionStatement ( invocations . Setter . Value . SendSuper ) }
439
+ }}
440
+ { ExpressionStatement ( KeepAlive ( "this" ) ) }
441
+ " ) ;
442
+ // perform the post delegate call conversion, this might include the GC.KeepAlive calls to keep
443
+ // the native object alive
444
+ setterBlock . Write ( invocations . Setter . Value . Argument . PostCallConversion , verifyTrivia : false ) ;
445
+ // mark property as dirty if needed
446
+ if ( property . RequiresDirtyCheck || property . IsWeakDelegate ) {
447
+ setterBlock . WriteLine ( "MarkDirty ();" ) ;
448
+ }
449
+
450
+ if ( property . NeedsBackingField ) {
451
+ setterBlock . WriteLine ( $ "{ property . BackingField } = value;") ;
452
+ }
453
+ }
454
+ }
455
+
456
+ // if the property is a weak delegate and has the strong delegate type set, we need to emit the
457
+ // strong delegate property
458
+ if ( property is { IsProperty : true , IsWeakDelegate : true }
459
+ && ! property . ExportPropertyData . StrongDelegateType . IsNullOrDefault ) {
460
+ classBlock . WriteLine ( ) ;
461
+ var strongDelegate = property . ToStrongDelegate ( ) ;
462
+ using ( var propertyBlock =
463
+ classBlock . CreateBlock ( strongDelegate . ToDeclaration ( ) . ToString ( ) , block : true ) ) {
464
+ using ( var getterBlock =
465
+ propertyBlock . CreateBlock ( "get" , block : true ) ) {
466
+ getterBlock . WriteLine (
467
+ $ "return { property . Name } as { strongDelegate . ReturnType . WithNullable ( isNullable : false ) . GetIdentifierSyntax ( ) } ;") ;
468
+ }
469
+
470
+ using ( var setterBlock =
471
+ propertyBlock . CreateBlock ( "set" , block : true ) ) {
472
+ setterBlock . WriteRaw (
473
+ $@ "var rvalue = value as NSObject;
474
+ if (!(value is null) && rvalue is null) {{
475
+ throw new ArgumentException ($""The object passed of type {{value.GetType ()}} does not derive from NSObject"");
476
+ }}
477
+ { property . Name } = rvalue;
478
+ " ) ;
479
+ }
480
+ }
481
+ }
482
+ }
483
+
484
+ /// <summary>
485
+ /// Emit the code for all the properties in the class.
486
+ /// </summary>
487
+ /// <param name="self">The class emitter.</param>
488
+ /// <param name="context">The current binding context.</param>
489
+ /// <param name="classBlock">Current class block.</param>
490
+ public static void EmitProperties ( this IClassEmitter self , in BindingContext context , TabbedWriter < StringWriter > classBlock )
491
+ {
492
+ // use the binding context to decide if we need to insert the ui thread check
493
+ var uiThreadCheck = ( context . NeedsThreadChecks )
494
+ ? EnsureUiThread ( context . RootContext . CurrentPlatform ) : null ;
495
+
496
+ foreach ( var property in context . Changes . Properties . OrderBy ( p => p . Name ) ) {
497
+ EmitProperty ( self , in context , property , classBlock , uiThreadCheck ) ;
498
+ }
499
+ }
335
500
}
0 commit comments