Skip to content

Commit fd57ba5

Browse files
committed
implement #44
1 parent 311d17c commit fd57ba5

File tree

2 files changed

+81
-15
lines changed

2 files changed

+81
-15
lines changed

src/DynamicObj/DynObj.fs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,34 @@ module DynObj =
216216
/// Prints a formatted string containing all static and dynamic properties of the given DynamicObj
217217
/// </summary>
218218
/// <param name="dynObj">The DynamicObj for which to print a formatted string for</param>
219-
let print (dynObj:DynamicObj) = printfn "%s" (dynObj |> format)
219+
let print (dynObj:DynamicObj) = printfn "%s" (dynObj |> format)
220+
221+
/// <summary>
222+
/// function to deep copy a boxed object (if possible)
223+
///
224+
/// The following cases are handled (in this precedence):
225+
///
226+
/// - Basic F# types (bool, byte, sbyte, int16, uint16, int, uint, int64, uint64, nativeint, unativeint, float, float32, char, string, unit, decimal)
227+
///
228+
/// - ResizeArrays and Dictionaries containing any combination of basic F# types
229+
///
230+
/// - Dictionaries containing DynamicObj as keys or values in any combination with DynamicObj or basic F# types as keys or values
231+
///
232+
/// - array&lt;DynamicObj&gt;, list&lt;DynamicObj&gt;, ResizeArray&lt;DynamicObj&gt;: These collections of DynamicObj are copied as a new collection with recursively deep copied elements.
233+
///
234+
/// - System.ICloneable: If the property implements ICloneable, the Clone() method is called on the property.
235+
///
236+
/// - DynamicObj (and derived classes): properties that are themselves DynamicObj instances are deep copied recursively.
237+
/// if a derived class has static properties (e.g. instance properties), these will be copied as dynamic properties on the new instance.
238+
///
239+
/// Note on Classes that inherit from DynamicObj:
240+
///
241+
/// Classes that inherit from DynamicObj will match the `DynamicObj` typecheck if they do not implement ICloneable.
242+
/// The deep copied instances will be cast to DynamicObj with static/instance properties AND dynamic properties all set as dynamic properties.
243+
/// It should be possible to 'recover' the original type by checking if the needed properties exist as dynamic properties,
244+
/// and then passing them to the class constructor if needed.
245+
/// </summary>
246+
/// <param name="o">The object that should be deep copied</param>
247+
/// <param name="includeInstanceProperties">Whether to include instance properties (= 'static' properties on the class) as dynamic properties on the new instance for matched DynamicObj.</param>
248+
let tryDeepCopyObj (includeInstanceProperties:bool) (o:DynamicObj) =
249+
CopyUtils.tryDeepCopyObj(o, includeInstanceProperties)

src/DynamicObj/DynamicObj.fs

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -283,16 +283,21 @@ type DynamicObj() =
283283
/// Note on Classes that inherit from DynamicObj:
284284
///
285285
/// Classes that inherit from DynamicObj will match the `DynamicObj` typecheck if they do not implement ICloneable.
286-
/// The deep coopied instances will be cast to DynamicObj with static/instance properties AND dynamic properties all set as dynamic properties.
286+
/// The deep copied instances will be cast to DynamicObj with static/instance properties AND dynamic properties all set as dynamic properties.
287287
/// It should be possible to 'recover' the original type by checking if the needed properties exist as dynamic properties,
288288
/// and then passing them to the class constructor if needed.
289289
/// </summary>
290290
/// <param name="target">The target object to copy dynamic members to</param>
291-
/// <param name="overWrite">Whether existing properties on the target object will be overwritten</param>
292-
member this.DeepCopyPropertiesTo(target:#DynamicObj, ?overWrite) =
291+
/// <param name="overWrite">Whether existing properties on the target object will be overwritten. Default is false</param>
292+
/// <param name="includeInstanceProperties">Whether to include instance properties (= 'static' properties on the class) as dynamic properties on the new instance. Default is true</param>
293+
member this.DeepCopyPropertiesTo(
294+
target:#DynamicObj,
295+
?overWrite: bool,
296+
?includeInstanceProperties:bool
297+
) =
293298
let overWrite = defaultArg overWrite false
294-
295-
this.GetProperties(true)
299+
let includeInstanceProperties = defaultArg includeInstanceProperties true
300+
this.GetProperties(includeInstanceProperties)
296301
|> Seq.iter (fun kv ->
297302
match target.TryGetPropertyHelper kv.Key with
298303
| Some pi when overWrite -> pi.SetValue target (CopyUtils.tryDeepCopyObj kv.Value)
@@ -327,19 +332,20 @@ type DynamicObj() =
327332
/// Note on Classes that inherit from DynamicObj:
328333
///
329334
/// Classes that inherit from DynamicObj will match the `DynamicObj` typecheck if they do not implement ICloneable.
330-
/// The deep coopied instances will be cast to DynamicObj with static/instance properties AND dynamic properties all set as dynamic properties.
335+
/// The deep copied instances will be cast to DynamicObj with static/instance properties AND dynamic properties all set as dynamic properties.
331336
/// It should be possible to 'recover' the original type by checking if the needed properties exist as dynamic properties,
332337
/// and then passing them to the class constructor if needed.
333338
/// </summary>
334-
/// <param name="target">The target object to copy dynamic members to</param>
335-
/// <param name="overWrite">Whether existing properties on the target object will be overwritten</param>
336-
member this.DeepCopyProperties() = CopyUtils.tryDeepCopyObj this
339+
/// <param name="includeInstanceProperties">Whether to include instance properties (= 'static' properties on the class) as dynamic properties on the new instance. Default is true</param>
340+
member this.DeepCopyProperties(?includeInstanceProperties:bool) =
341+
let includeInstanceProperties = defaultArg includeInstanceProperties true
342+
CopyUtils.tryDeepCopyObj(this, includeInstanceProperties)
337343

338344
#if !FABLE_COMPILER
339345
// Some necessary overrides for methods inherited from System.Dynamic.DynamicObject()
340346
//
341347
// Needed mainly for making Newtonsoft.Json Serialization work
342-
override this.TryGetMember(binder:GetMemberBinder,result:obj byref ) =
348+
override this.TryGetMember(binder:GetMemberBinder,result:obj byref) =
343349
match this.TryGetPropertyValue binder.Name with
344350
| Some value -> result <- value; true
345351
| None -> false
@@ -396,8 +402,39 @@ type DynamicObj() =
396402

397403
and CopyUtils =
398404

399-
/// internal helper function to deep copy a boxed object (if possible)
400-
static member tryDeepCopyObj (o:obj) =
405+
/// <summary>
406+
/// function to deep copy a boxed object (if possible)
407+
408+
/// The following cases are handled (in this precedence):
409+
///
410+
/// - Basic F# types (bool, byte, sbyte, int16, uint16, int, uint, int64, uint64, nativeint, unativeint, float, float32, char, string, unit, decimal)
411+
///
412+
/// - ResizeArrays and Dictionaries containing any combination of basic F# types
413+
///
414+
/// - Dictionaries containing DynamicObj as keys or values in any combination with DynamicObj or basic F# types as keys or values
415+
///
416+
/// - array&lt;DynamicObj&gt;, list&lt;DynamicObj&gt;, ResizeArray&lt;DynamicObj&gt;: These collections of DynamicObj are copied as a new collection with recursively deep copied elements.
417+
///
418+
/// - System.ICloneable: If the property implements ICloneable, the Clone() method is called on the property.
419+
///
420+
/// - DynamicObj (and derived classes): properties that are themselves DynamicObj instances are deep copied recursively.
421+
/// if a derived class has static properties (e.g. instance properties), these will be copied as dynamic properties on the new instance.
422+
///
423+
/// Note on Classes that inherit from DynamicObj:
424+
///
425+
/// Classes that inherit from DynamicObj will match the `DynamicObj` typecheck if they do not implement ICloneable.
426+
/// The deep copied instances will be cast to DynamicObj with static/instance properties AND dynamic properties all set as dynamic properties.
427+
/// It should be possible to 'recover' the original type by checking if the needed properties exist as dynamic properties,
428+
/// and then passing them to the class constructor if needed.
429+
/// </summary>
430+
/// <param name="o">The object that should be deep copied</param>
431+
/// <param name="includeInstanceProperties">Whether to include instance properties (= 'static' properties on the class) as dynamic properties on the new instance for matched DynamicObj. Default is true</param>
432+
static member tryDeepCopyObj(
433+
o:obj,
434+
?includeInstanceProperties:bool
435+
) =
436+
let includeInstanceProperties = defaultArg includeInstanceProperties true
437+
401438
let rec tryDeepCopyObj (o:obj) =
402439
match o with
403440

@@ -949,8 +986,7 @@ and CopyUtils =
949986

950987
| :? DynamicObj as dyn ->
951988
let newDyn = DynamicObj()
952-
// might want to keep instance props as dynamic props on copy
953-
for kv in (dyn.GetProperties(true)) do
989+
for kv in (dyn.GetProperties(includeInstanceProperties)) do
954990
newDyn.SetProperty(kv.Key, tryDeepCopyObj kv.Value)
955991
box newDyn
956992
| _ -> o

0 commit comments

Comments
 (0)