44
55namespace Python . Test ;
66
7- public class DynamicMappingObject : DynamicObject
7+ /// <summary>
8+ /// Base class for dynamic test helpers. Uses lazy storage initialization so that
9+ /// Python-derived subclasses can safely call DynamicObject member hooks before
10+ /// managed field initializers have run.
11+ /// </summary>
12+ public class DynamicStorageObject : DynamicObject
813{
914 Dictionary < string , object > storage ;
1015
11- Dictionary < string , object > Storage => storage ??= [ ] ;
16+ // Python-defined subclasses may reach this type without running managed field
17+ // initializers (see ClassDerivedObject.NewObjectToPython). Via the lazy init
18+ // we can ensure that the access is still safe, even when the constructor has
19+ // not run.
20+ protected Dictionary < string , object > Storage => storage ??= [ ] ;
1221
22+ public void AddDynamicMember ( string name , object value ) => Storage [ name ] = value ;
23+
24+ public override bool TryGetMember ( GetMemberBinder binder , out object result )
25+ => Storage . TryGetValue ( binder . Name , out result ) ;
26+
27+ public override bool TrySetMember ( SetMemberBinder binder , object value )
28+ {
29+ Storage [ binder . Name ] = value ;
30+ return true ;
31+ }
32+
33+ public override bool TryDeleteMember ( DeleteMemberBinder binder )
34+ => Storage . Remove ( binder . Name ) ;
35+
36+ public override IEnumerable < string > GetDynamicMemberNames ( ) => Storage . Keys ;
37+ }
38+
39+ public class DynamicMappingObject : DynamicStorageObject
40+ {
1341 // Native members for testing that regular CLR access is unaffected.
1442 public string Label = "default" ;
1543 public int Multiplier { get ; set ; } = 1 ;
@@ -20,20 +48,39 @@ public class DynamicMappingObject : DynamicObject
2048
2149 // Test helper: retrieve the actual value stored in C# (for verification that None was stored as null)
2250 public object GetDynamicValue ( string name ) => Storage . TryGetValue ( name , out var value ) ? value : null ;
51+ }
2352
24-
25-
26- public override bool TryGetMember ( GetMemberBinder binder , out object result )
27- => Storage . TryGetValue ( binder . Name , out result ) ;
28-
53+ public class RejectingSetDynamicObject : DynamicStorageObject
54+ {
2955 public override bool TrySetMember ( SetMemberBinder binder , object value )
3056 {
57+ if ( ! Storage . ContainsKey ( binder . Name ) )
58+ return false ;
59+
3160 Storage [ binder . Name ] = value ;
3261 return true ;
3362 }
63+ }
64+
65+ public class ThrowingSetDynamicObject : DynamicStorageObject
66+ {
67+ public override bool TrySetMember ( SetMemberBinder binder , object value )
68+ => throw new InvalidOperationException ( $ "TrySetMember failed for '{ binder . Name } '") ;
69+ }
3470
71+ public class RejectingDeleteDynamicObject : DynamicStorageObject
72+ {
3573 public override bool TryDeleteMember ( DeleteMemberBinder binder )
36- => binder is not null && Storage . Remove ( binder . Name ) ;
74+ {
75+ if ( ! Storage . ContainsKey ( binder . Name ) )
76+ return false ;
3777
38- public override IEnumerable < string > GetDynamicMemberNames ( ) => Storage . Keys ;
78+ return Storage . Remove ( binder . Name ) ;
79+ }
80+ }
81+
82+ public class ThrowingDeleteDynamicObject : DynamicStorageObject
83+ {
84+ public override bool TryDeleteMember ( DeleteMemberBinder binder )
85+ => throw new InvalidOperationException ( $ "TryDeleteMember failed for '{ binder . Name } '") ;
3986}
0 commit comments