@@ -174,33 +174,39 @@ public Size BindAsObject(in BindContext context, object? value, ref object? writ
174174 try
175175 {
176176 size = BindValueAsObject ( context , value , ref writeState ) ;
177+
178+ if ( ( originalWriteState is not null || context . IsBindFixedSize ) && ! ReferenceEquals ( originalWriteState , writeState )
179+ && ( context . IsBindFixedSize || writeState is null || originalWriteState is IDisposable ) )
180+ ThrowWriteStateLifecycleViolation ( context . IsBindFixedSize ) ;
181+
182+ // Catches non-Exact sizes from both paths (the IsBindFixedSize Kind check is folded in here —
183+ // a converter declaring fixed-size that returned a non-Exact size trips this same throw).
184+ switch ( size . Kind )
185+ {
186+ case SizeKind . UpperBound :
187+ ThrowHelper . ThrowInvalidOperationException ( $ "{ nameof ( SizeKind . UpperBound ) } is not a valid return value for BindValue.") ;
188+ break ;
189+ case SizeKind . Unknown :
190+ ThrowHelper . ThrowInvalidOperationException ( $ "{ nameof ( SizeKind . Unknown ) } is not a valid return value for BindValue.") ;
191+ break ;
192+ }
177193 }
178194 catch
179195 {
180196 // Contract: writeState transitions to null on throw. BindValue is free to assign partial
181197 // state to writeState as it works (composing converters do this so their wrapper's Dispose
182198 // can clean up populated slots). The framework's safety net here disposes anything still
183- // in writeState and nulls it, so callers see a uniform "clean on throw" semantic.
184- if ( writeState is IDisposable d )
185- d . Dispose ( ) ;
186- writeState = null ;
199+ // observable to us and nulls the slot, so callers see a uniform "clean on throw" semantic.
200+ // Both current and original may need disposing on a lifecycle-violation swap; the
201+ // ReferenceEquals gate collapses to a single Dispose on the legal no-swap case.
202+ // Null first, then dispose: a throwing Dispose must not leave callers with a non-null
203+ // writeState pointing at a half-disposed object — they'd dispose it again.
204+ ( var current , writeState ) = ( writeState , null ) ;
205+ ( current as IDisposable ) ? . Dispose ( ) ;
206+ if ( ! ReferenceEquals ( current , originalWriteState ) )
207+ ( originalWriteState as IDisposable ) ? . Dispose ( ) ;
187208 throw ;
188209 }
189- if ( ( originalWriteState is not null || context . IsBindFixedSize ) && ! ReferenceEquals ( originalWriteState , writeState )
190- && ( context . IsBindFixedSize || writeState is null || originalWriteState is IDisposable ) )
191- ThrowWriteStateLifecycleViolation ( context . IsBindFixedSize ) ;
192-
193- // Catches non-Exact sizes from both paths (the IsBindFixedSize Kind check is folded in here —
194- // a converter declaring fixed-size that returned a non-Exact size trips this same throw).
195- switch ( size . Kind )
196- {
197- case SizeKind . UpperBound :
198- ThrowHelper . ThrowInvalidOperationException ( $ "{ nameof ( SizeKind . UpperBound ) } is not a valid return value for BindValue.") ;
199- break ;
200- case SizeKind . Unknown :
201- ThrowHelper . ThrowInvalidOperationException ( $ "{ nameof ( SizeKind . Unknown ) } is not a valid return value for BindValue.") ;
202- break ;
203- }
204210
205211 return size ;
206212 }
@@ -344,31 +350,37 @@ public Size Bind(in BindContext context,
344350 try
345351 {
346352 size = BindValue ( context , value , ref writeState ) ;
353+
354+ if ( ( originalWriteState is not null || context . IsBindFixedSize ) && ! ReferenceEquals ( originalWriteState , writeState )
355+ && ( context . IsBindFixedSize || writeState is null || originalWriteState is IDisposable ) )
356+ ThrowWriteStateLifecycleViolation ( context . IsBindFixedSize ) ;
357+
358+ switch ( size . Kind )
359+ {
360+ case SizeKind . UpperBound :
361+ ThrowHelper . ThrowInvalidOperationException ( $ "{ nameof ( SizeKind . UpperBound ) } is not a valid return value for { nameof ( BindValue ) } .") ;
362+ break ;
363+ case SizeKind . Unknown :
364+ ThrowHelper . ThrowInvalidOperationException ( $ "{ nameof ( SizeKind . Unknown ) } is not a valid return value for { nameof ( BindValue ) } .") ;
365+ break ;
366+ }
347367 }
348368 catch
349369 {
350370 // Contract: writeState transitions to null on throw. BindValue is free to assign partial
351371 // state to writeState as it works (composing converters do this so their wrapper's Dispose
352372 // can clean up populated slots). The framework's safety net here disposes anything still
353- // in writeState and nulls it, so callers see a uniform "clean on throw" semantic.
354- if ( writeState is IDisposable d )
355- d . Dispose ( ) ;
356- writeState = null ;
373+ // observable to us and nulls the slot, so callers see a uniform "clean on throw" semantic.
374+ // Both current and original may need disposing on a lifecycle-violation swap; the
375+ // ReferenceEquals gate collapses to a single Dispose on the legal no-swap case.
376+ // Null first, then dispose: a throwing Dispose must not leave callers with a non-null
377+ // writeState pointing at a half-disposed object — they'd dispose it again.
378+ ( var current , writeState ) = ( writeState , null ) ;
379+ ( current as IDisposable ) ? . Dispose ( ) ;
380+ if ( ! ReferenceEquals ( current , originalWriteState ) )
381+ ( originalWriteState as IDisposable ) ? . Dispose ( ) ;
357382 throw ;
358383 }
359- if ( ( originalWriteState is not null || context . IsBindFixedSize ) && ! ReferenceEquals ( originalWriteState , writeState )
360- && ( context . IsBindFixedSize || writeState is null || originalWriteState is IDisposable ) )
361- ThrowWriteStateLifecycleViolation ( context . IsBindFixedSize ) ;
362-
363- switch ( size . Kind )
364- {
365- case SizeKind . UpperBound :
366- ThrowHelper . ThrowInvalidOperationException ( $ "{ nameof ( SizeKind . UpperBound ) } is not a valid return value for { nameof ( BindValue ) } .") ;
367- break ;
368- case SizeKind . Unknown :
369- ThrowHelper . ThrowInvalidOperationException ( $ "{ nameof ( SizeKind . Unknown ) } is not a valid return value for { nameof ( BindValue ) } .") ;
370- break ;
371- }
372384
373385 return size ;
374386 }
0 commit comments