@@ -312,27 +312,22 @@ parentExpression.Expression is ConstantExpression instanceExpression &&
312
312
/// public Task MyTask
313
313
/// {
314
314
/// get => myTask;
315
- /// private set => SetAndNotifyOnCompletion(ref myTask, () => myTask, value);
315
+ /// private set => SetAndNotifyOnCompletion(() => ref myTask, value);
316
316
/// }
317
317
/// </code>
318
318
/// </summary>
319
319
/// <typeparam name="TTask">The type of <see cref="Task"/> to set and monitor.</typeparam>
320
- /// <param name="field">The field storing the property's value.</param>
321
- /// <param name="fieldExpression">
322
- /// An <see cref="Expression{TDelegate}"/> returning the field to update. This is needed to be
323
- /// able to raise the <see cref="PropertyChanged"/> to notify the completion of the input task.
324
- /// </param>
320
+ /// <param name="fieldAccessor">The <see cref="FieldAccessor{T}"/> instance to access the backing field for the property.</param>
325
321
/// <param name="newValue">The property's value after the change occurred.</param>
326
322
/// <param name="propertyName">(optional) The name of the property that changed.</param>
327
323
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
328
324
/// <remarks>
329
- /// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not raised if the current
330
- /// and new value for the target property are the same. The return value being <see langword="true"/> only
331
- /// indicates that the new value being assigned to <paramref name="field "/> is different than the previous one,
325
+ /// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not raised if the current and new
326
+ /// value for the target property are the same. The return value being <see langword="true"/> only indicates that the
327
+ /// new value being assigned to field provided by <paramref name="fieldAccessor "/> is different than the previous one,
332
328
/// and it does not mean the new <typeparamref name="TTask"/> instance passed as argument is in any particular state.
333
329
/// </remarks>
334
- /// <exception cref="ArgumentException">Thrown when <paramref name="fieldExpression"/> is not valid.</exception>
335
- protected bool SetPropertyAndNotifyOnCompletion < TTask > ( ref TTask ? field , Expression < Func < TTask ? > > fieldExpression , TTask ? newValue , [ CallerMemberName ] string ? propertyName = null )
330
+ protected bool SetPropertyAndNotifyOnCompletion < TTask > ( FieldAccessor < TTask ? > fieldAccessor , TTask ? newValue , [ CallerMemberName ] string ? propertyName = null )
336
331
where TTask : Task
337
332
{
338
333
// We invoke the overload with a callback here to avoid code duplication, and simply pass an empty callback.
@@ -341,21 +336,19 @@ protected bool SetPropertyAndNotifyOnCompletion<TTask>(ref TTask? field, Express
341
336
// instance. This will result in no further allocations after the first time this method is called for a given
342
337
// generic type. We only pay the cost of the virtual call to the delegate, but this is not performance critical
343
338
// code and that overhead would still be much lower than the rest of the method anyway, so that's fine.
344
- return SetPropertyAndNotifyOnCompletion ( ref field , fieldExpression , newValue , _ => { } , propertyName ) ;
339
+ return SetPropertyAndNotifyOnCompletion ( fieldAccessor , newValue , _ => { } , propertyName ) ;
345
340
}
346
341
347
342
/// <summary>
348
343
/// Compares the current and new values for a given field (which should be the backing
349
344
/// field for a property). If the value has changed, raises the <see cref="PropertyChanging"/>
350
345
/// event, updates the field and then raises the <see cref="PropertyChanged"/> event.
351
- /// This method is just like <see cref="SetPropertyAndNotifyOnCompletion{TTask}(ref TTask,Expression{Func{ TTask} },TTask,string)"/>,
346
+ /// This method is just like <see cref="SetPropertyAndNotifyOnCompletion{TTask}(FieldAccessor{ TTask},TTask,string)"/>,
352
347
/// with the difference being an extra <see cref="Action{T}"/> parameter with a callback being invoked
353
348
/// either immediately, if the new task has already completed or is <see langword="null"/>, or upon completion.
354
349
/// </summary>
355
350
/// <typeparam name="TTask">The type of <see cref="Task"/> to set and monitor.</typeparam>
356
- /// <param name="field">The field storing the property's value.</param>
357
- /// <param name="fieldExpression">
358
- /// An <see cref="Expression{TDelegate}"/> returning the field to update.</param>
351
+ /// <param name="fieldAccessor">The <see cref="FieldAccessor{T}"/> instance to access the backing field for the property.</param>
359
352
/// <param name="newValue">The property's value after the change occurred.</param>
360
353
/// <param name="callback">A callback to invoke to update the property value.</param>
361
354
/// <param name="propertyName">(optional) The name of the property that changed.</param>
@@ -364,10 +357,12 @@ protected bool SetPropertyAndNotifyOnCompletion<TTask>(ref TTask? field, Express
364
357
/// The <see cref="PropertyChanging"/> and <see cref="PropertyChanged"/> events are not raised
365
358
/// if the current and new value for the target property are the same.
366
359
/// </remarks>
367
- /// <exception cref="ArgumentException">Thrown when <paramref name="fieldExpression"/> is not valid.</exception>
368
- protected bool SetPropertyAndNotifyOnCompletion < TTask > ( ref TTask ? field , Expression < Func < TTask ? > > fieldExpression , TTask ? newValue , Action < TTask ? > callback , [ CallerMemberName ] string ? propertyName = null )
360
+ protected bool SetPropertyAndNotifyOnCompletion < TTask > ( FieldAccessor < TTask ? > fieldAccessor , TTask ? newValue , Action < TTask ? > callback , [ CallerMemberName ] string ? propertyName = null )
369
361
where TTask : Task
370
362
{
363
+ // Invoke the accessor once to get a field reference for the synchronous part
364
+ ref TTask ? field = ref fieldAccessor ( ) ;
365
+
371
366
if ( ReferenceEquals ( field , newValue ) )
372
367
{
373
368
return false ;
@@ -398,16 +393,6 @@ protected bool SetPropertyAndNotifyOnCompletion<TTask>(ref TTask? field, Express
398
393
return true ;
399
394
}
400
395
401
- // Get the target field to set. This is needed because we can't
402
- // capture the ref field in a closure (for the async method).
403
- if ( ! ( ( fieldExpression . Body as MemberExpression ) ? . Member is FieldInfo fieldInfo ) )
404
- {
405
- ThrowArgumentExceptionForInvalidFieldExpression ( ) ;
406
-
407
- // This is never executed, as the method above always throws
408
- return false ;
409
- }
410
-
411
396
// We use a local async function here so that the main method can
412
397
// remain synchronous and return a value that can be immediately
413
398
// used by the caller. This mirrors Set<T>(ref T, T, string).
@@ -427,7 +412,7 @@ async void MonitorTask()
427
412
{
428
413
}
429
414
430
- TTask ? currentTask = ( TTask ? ) fieldInfo . GetValue ( this ) ;
415
+ TTask ? currentTask = fieldAccessor ( ) ;
431
416
432
417
// Only notify if the property hasn't changed
433
418
if ( ReferenceEquals ( newValue , currentTask ) )
@@ -444,19 +429,18 @@ async void MonitorTask()
444
429
}
445
430
446
431
/// <summary>
447
- /// Throws an <see cref="ArgumentException "/> when a given <see cref="Expression{TDelegate}"/> is invalid for a property.
432
+ /// A custom <see langword="delegate "/> that returns a reference to a backing field to a property.
448
433
/// </summary>
449
- private static void ThrowArgumentExceptionForInvalidPropertyExpression ( )
450
- {
451
- throw new ArgumentException ( "The given expression must be in the form () => MyModel.MyProperty" ) ;
452
- }
434
+ /// <typeparam name="T">The type of reference to return.</typeparam>
435
+ /// <returns>A reference to the backing field of a property.</returns>
436
+ protected delegate ref T FieldAccessor < T > ( ) ;
453
437
454
438
/// <summary>
455
- /// Throws an <see cref="ArgumentException"/> when a given <see cref="Expression{TDelegate}"/> is invalid for a property field .
439
+ /// Throws an <see cref="ArgumentException"/> when a given <see cref="Expression{TDelegate}"/> is invalid for a property.
456
440
/// </summary>
457
- private static void ThrowArgumentExceptionForInvalidFieldExpression ( )
441
+ private static void ThrowArgumentExceptionForInvalidPropertyExpression ( )
458
442
{
459
- throw new ArgumentException ( "The given expression must be in the form () => field " ) ;
443
+ throw new ArgumentException ( "The given expression must be in the form () => MyModel.MyProperty " ) ;
460
444
}
461
445
}
462
446
}
0 commit comments