/// <summary> /// Populate the fields of this object by copying them from equivalent fields on /// another object. The fields to be copied must share names and types in order to /// be successfully transferred. /// The CopyAttribute will be used to determine the correct behaviour when copying fields /// accross - first on the field itself and then, if not set, on the type of the field. /// If neither of these is specified the default is to do a 'shallow' or reference-copy on all objects /// except for collection type, where the default is to not copy any fields *unless* they are /// specifically annotated. /// </summary> /// <param name="source">The object to copy fields from.</param> /// <param name="objectMap">A map of original objects to their copies. Used when duplicating multiple /// objects at once to create links between them of the same relative relationships.</param> public static void CopyFieldsFrom(this object target, object source, ref Dictionary <object, object> objectMap) { Type targetType = target.GetType(); Type sourceType = source.GetType(); BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy; ICollection <FieldInfo> fields = targetType.GetAllFields(flags); foreach (FieldInfo targetField in fields) { FieldInfo sourceField = sourceType.GetBaseField(targetField.Name, flags); if (sourceField != null && targetField.FieldType.IsAssignableFrom(sourceField.FieldType)) { // Have found a matching property - check for copy behaviour attributes: // Currently this is done on the source field. Might it also be safer to check the target // field as well, for at least certain values? CopyAttribute copyAtt = sourceField.GetAttribute <CopyAttribute>(); // If copy attribute is not set on the field, we will try it on the type: if (copyAtt == null) { copyAtt = sourceField.FieldType.GetCustomAttribute <CopyAttribute>(); } CopyBehaviour behaviour = CopyBehaviour.COPY; CopyBehaviour itemsBehaviour = CopyBehaviour.COPY; if (sourceType.IsCollection()) { behaviour = CopyBehaviour.DO_NOT_COPY; //By default, do not copy fields from collection types } if (copyAtt != null) { behaviour = copyAtt.Behaviour; if (copyAtt is CollectionCopyAttribute) { itemsBehaviour = ((CollectionCopyAttribute)copyAtt).ItemsBehaviour; } } if (behaviour != CopyBehaviour.DO_NOT_COPY) { object value = sourceField.GetValue(source); value = ValueToAssign(value, ref behaviour, itemsBehaviour, ref objectMap); if (behaviour != CopyBehaviour.DO_NOT_COPY) { targetField.SetValue(target, value); } } } } }
/// <summary> /// Produce a duplicated copy of this object. /// Family references will be copied, save for those which /// are intended to be unique to this object, which will themselves /// be duplicated. /// </summary> /// <param name="objectMap">The map of original objects to duplicated objects.</param> /// <returns>A duplicated copy of this object</returns> public static T Duplicate <T>(this T obj, ref Dictionary <object, object> objectMap, CopyBehaviour itemsBehaviour = CopyBehaviour.COPY) where T : IDuplicatable { T clone; if (obj.GetType().HasParameterlessConstructor()) { clone = (T)Activator.CreateInstance(obj.GetType(), true); //Create a blank instance of the relevant type } else { #if !JS // As a (potentially dangerous) fallback: clone = (T)FormatterServices.GetUninitializedObject(obj.GetType()); //Special cludge for Uniques to avoid having all-0 GUIDs: if (clone is IUniqueWithModifiableGUID) { ((IUniqueWithModifiableGUID)clone).SetGUID(Guid.NewGuid()); } #else throw new NotSupportedException("Class to be duplicated does not provide a parameterless constructor!"); #endif } if (objectMap == null) { objectMap = new Dictionary <object, object>(); } objectMap[obj] = clone; //Store the original-clone relationship in the map clone.CopyFieldsFrom(obj, ref objectMap); if (obj.GetType().IsCollection() && itemsBehaviour != CopyBehaviour.DO_NOT_COPY) { ICollection source = (ICollection)obj; ICollection target = (ICollection)clone; foreach (object item in source) { CopyBehaviour behaviour = itemsBehaviour; CopyBehaviour subItemsBehaviour = itemsBehaviour; if (item != null && item is IDuplicatable) { CopyAttribute cAtt = item.GetType().GetCustomAttribute <CopyAttribute>(); if (cAtt != null) { behaviour = cAtt.Behaviour; if (cAtt is CollectionCopyAttribute) { subItemsBehaviour = ((CollectionCopyAttribute)cAtt).ItemsBehaviour; } } } object value = ValueToAssign(item, ref behaviour, subItemsBehaviour, ref objectMap); if (target is IList) { ((IList)target).Add(value); } // TODO: Other types of collections? } } return(clone); }