using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; namespace Quadarax.Foundation.Core.Object.Dynamic { /// /// Provides a dynamic object implementation that allows for runtime definition and manipulation of properties. /// /// /// The Dyna class offers a flexible way to create objects with dynamic property sets that can be /// defined at runtime. It supports property access control, value comparison, and cloning capabilities. /// public class Dyna : IDyna { #region *** Private Fields *** /// /// Thread-safe dictionary storing the dynamic properties and their values. /// private IDictionary _properties = new ConcurrentDictionary(); private IList _changedPropertes = new List(); private IList> _defaultValues = new List>(); #endregion #region *** Properties *** /// /// Gets an array of all property definitions contained in this Dyna instance. /// /// An array of objects representing the properties. public IDynaProperty[] Properties => _properties.Values.Select(x => x.Property).ToArray(); /// /// Gets an array of all property names changed after calling TrackChanges. /// /// An array of contains property names. public string[] ChangedProperties => _changedPropertes.ToArray(); /// /// Gets or sets the value of a property by its name. /// /// The name of the property to access. /// The value of the specified property. /// Thrown when the property does not exist. public object? this[string propertyName] { get => GetValue(propertyName); set => SetValue(propertyName, value); } #endregion #region *** Constructors *** /// /// Initializes a new instance of the class with no properties. /// public Dyna() { TrackChanges(); } /// /// Initializes a new instance of the class with properties derived from the specified type. /// /// The type whose properties should be reflected into this Dyna instance. /// Thrown when the template type does not have a valid FullName. public Dyna(Type templateType) { AnalyzeType(templateType); TrackChanges(); } /// /// Initializes a new instance of the class with the specified properties. /// /// A collection of objects defining the properties for this instance. public Dyna(IEnumerable properties) : this() { AnalyzeProperties(properties); TrackChanges(); } #endregion #region *** Public Operations *** /// /// Gets the value of a property as a specified type. /// /// The type to convert the property value to. /// The name of the property to get the value from. /// The value of the property cast to the specified type, or null if the property value is null. /// Thrown when the property does not exist. public TValue? GetValue(string propertyName) { if (_properties.TryGetValue(propertyName, out var property)) { if (property.Value == null) return default; return (TValue)property.Value; } throw new ArgumentOutOfRangeException(nameof(propertyName), $"Property '{propertyName}' not found in Dyna collection."); } /// /// Sets the value of a property. /// /// The type of the value being set. /// The name of the property to set. /// The value to set the property to. /// Thrown when the property does not exist. public void SetValue(string propertyName, TValue? value) { if (_properties.TryGetValue(propertyName, out var property)) { if (!object.Equals(property.Value, value)) { // check if new value is the same as the default value, then discard the change if (object.Equals(value, _defaultValues.First(x=>x.Item1 == propertyName).Item2)) { property.Value = value; _changedPropertes.Remove(propertyName); return; } if (OnChangingProperty(propertyName, property.Value, value)) { property.Value = value; // track changes if (!_changedPropertes.Contains(propertyName)) _changedPropertes.Add(propertyName); } } } else throw new ArgumentOutOfRangeException(nameof(propertyName), $"Property '{propertyName}' not found in Dyna collection."); } /// /// Clears all property values, setting them to null. /// public void Clear() { foreach (var property in _properties) property.Value.Value = null; OnClear(); TrackChanges(); } /// /// Tracks all property changes after calling this method. /// public void TrackChanges() { _changedPropertes.Clear(); _defaultValues.Clear(); // create new snapshot of default values foreach (var property in _properties) _defaultValues.Add(new Tuple(property.Key, property.Value.Value)); } /// /// Determines whether this Dyna instance has the same property structure as another Dyna instance. /// /// The Dyna instance to compare with. /// /// true if both instances have the same property names and types; otherwise, false. /// public bool IsSame(Dyna target) { if (_properties.Count != target._properties.Count) return false; foreach (var property in _properties) { if (!target._properties.TryGetValue(property.Key, out var targetProperty)) return false; } return true; } /// /// Determines whether this Dyna instance has the same properties and values as another Dyna instance. /// /// The Dyna instance to compare with. /// /// true if both instances have the same properties and values; otherwise, false. /// public bool IsEquals(Dyna target) { if (_properties.Count != target._properties.Count) return false; foreach (var property in _properties) { if (!target._properties.TryGetValue(property.Key, out var targetProperty)) return false; if (!Equals(property.Value.Value, targetProperty.Value)) return false; } return true; } /// /// Determines whether this Dyna instance contains a property with the specified name. /// /// The name of the property to check for. /// true if the property exists; otherwise, false. public bool ContainsProperty(string propertyName) { return _properties.ContainsKey(propertyName); } /// /// Copies all property values from this Dyna instance to a target Dyna instance. /// /// The target Dyna instance to copy values to. /// /// This method only copies values for properties that exist in both instances. /// It does not create new properties in the target instance. /// public void CopyTo(IDyna target) { foreach (var property in _properties) target.SetValue(property.Key, property.Value.Value); } /// /// Creates a deep clone of this Dyna instance. /// /// A new Dyna instance with the same properties and values. public object Clone() { var clone = new Dyna(_properties.Values.Select(x => x.Property)); foreach (var property in _properties) clone.SetValue(property.Key, property.Value.Value); clone.TrackChanges(); return clone; } #endregion #region *** Virtual Operations *** public virtual bool OnChangingProperty(string propertyName, object? oldValue, object? newValue) { return true; } public virtual void OnClear() { } #endregion #region *** Private Operations *** /// /// Analyzes a type using reflection and creates corresponding dynamic properties. /// /// The type to analyze. /// Thrown when the template type does not have a valid FullName. private void AnalyzeType(Type templateType) { if (templateType.FullName == null) throw new ArgumentException("Type must have a FullName", nameof(templateType)); foreach (var property in templateType.GetProperties()) _properties.Add(property.Name, new DynaPropertyWithValue(new DynaProperty(property.Name, property.PropertyType, DynaPropertyAccessability.Public, DynaPropertyAccessability.Public), null)); } /// /// Creates dynamic properties from a collection of property definitions. /// /// The collection of property definitions to use. private void AnalyzeProperties(IEnumerable properties) { foreach (var property in properties) _properties.Add(property.Name, new DynaPropertyWithValue(property, null)); } #endregion #region *** Nested Classes *** /// /// Represents a dynamic property with its metadata. /// public sealed class DynaProperty : IDynaProperty { #region *** Properties *** /// /// Gets the name of the property. /// public string Name { get; private set; } /// /// Gets the type of the property. /// public Type ValueType { get; private set; } /// /// Gets the accessibility level for reading the property. /// public DynaPropertyAccessability GetAccessability { get; private set; } /// /// Gets the accessibility level for writing to the property. /// public DynaPropertyAccessability SetAccessability { get; private set; } #endregion #region *** Constructors *** /// /// Initializes a new instance of the class. /// /// The name of the property. /// The type of the property. /// The accessibility level for reading the property. /// The accessibility level for writing to the property. public DynaProperty(string name, Type valueType, DynaPropertyAccessability getAccessability, DynaPropertyAccessability setAccessability) { Name = name; ValueType = valueType; GetAccessability = getAccessability; SetAccessability = setAccessability; } #endregion } /// /// Internal class that pairs a property definition with its value. /// private sealed class DynaPropertyWithValue { /// /// Gets the property definition. /// public DynaProperty Property { get; private set; } /// /// Gets or sets the current value of the property. /// public object? Value { get; set; } /// /// Initializes a new instance of the class. /// /// The property definition. /// The initial value of the property. public DynaPropertyWithValue(DynaProperty property, object? value) { Property = property; Value = value; } } #endregion } }