public void TestDefault() { // Automatic default var goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(bool), false, null, null, null); Assert.DoesNotThrow(() => goodProperty.Default.GetType()); // Provided factory goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(bool), false, null, () => true, null); Assert.IsTrue((bool)goodProperty.Default); // Faulty factory (null return for value type) var mustFail = new HierarchicalProperty(GetType(), "Foo", typeof(bool), false, null, () => null, null); Assert.Throws<PropertyException>(() => mustFail.Default.GetType()); // Faulty factory (the factory throws exception) mustFail = new HierarchicalProperty(GetType(), "Foo", typeof(bool), false, null, () => { throw null; }, null); Assert.Throws<PropertyException>(() => mustFail.Default.GetType()); // Faulty factory (returns the wront type) mustFail = new HierarchicalProperty(GetType(), "Foo", typeof(double), false, null, () => 1, null); Assert.Throws<PropertyException>(() => mustFail.Default.GetType()); }
public void TestCloner() { // The clone is the object itself (very efficient if the type is immutable) var goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(object), false, null, null, o => o); var obj = new object(); Assert.IsTrue(goodProperty.IsCloneable, "Cloner method provided"); Assert.AreSame(obj, goodProperty.Clone(obj)); // Must clone automatically goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(double), false, null, null, null); Assert.IsTrue(goodProperty.IsCloneable, "Is primitive"); Assert.AreEqual(14.0, (double)goodProperty.Clone(14.0)); // Must return null goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(object), false, null, null, null); Assert.IsFalse(goodProperty.IsCloneable, "Is object and no cloner provided"); Assert.IsNull(goodProperty.Clone(null)); // Must fail from type mismatch var mustFail = new HierarchicalProperty(GetType(), "Foo", typeof(double), false, null, null, o => (int)(double)o); Assert.IsTrue(mustFail.IsCloneable, "Cloner method provided"); Assert.Throws<ArgumentException>(() => mustFail.Clone(1)); // Wrong argument type Assert.Throws<PropertyException>(() => mustFail.Clone(1.0)); // Wrong type returned by the cloner }
public void TestParser() { // string returns itself var goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(string), false, null, null, null); var str = 1.ToString(); Assert.IsTrue(goodProperty.IsParsable, "Is string"); Assert.AreSame(str, goodProperty.Parse(str)); // Must parse automatically goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(double), false, null, null, null); Assert.IsTrue(goodProperty.IsParsable, "Is primitive"); Assert.AreEqual(14.0, (double)goodProperty.Parse("14.0")); // Must parse nullable types automatically too. goodProperty = new HierarchicalProperty(GetType(), "Foo", typeof(DateTime?), false, null, null, null); var now = DateTime.Now; Assert.IsTrue(goodProperty.IsParsable, "Is Nullable with an underlying type understood by the Convert"); Assert.AreEqual(Convert.ToDateTime(now.ToString(CultureInfo.InvariantCulture)), (DateTime)goodProperty.Parse(now.ToString(CultureInfo.InvariantCulture))); // Must throw on null var mustFail = new HierarchicalProperty(GetType(), "Foo", typeof(object), false, null, null, null); Assert.IsFalse(mustFail.IsParsable, "Is object and no parser provided"); Assert.Throws<ArgumentNullException>(() => mustFail.Parse(null)); // Must fail from type mismatch mustFail = new HierarchicalProperty(GetType(), "Foo", typeof(double), false, s => int.Parse(s), null, null); Assert.IsTrue(mustFail.IsParsable, "Parser method provided"); Assert.Throws<PropertyException>(() => mustFail.Parse("1")); // Wrong type returned by the cloner }
/// <summary> /// Register a new managed property. /// </summary> /// <returns>The representative for the property.</returns> /// <param name="owner">The type that owns this property</param> /// <param name="name">Name of the property (preferably same as it's CLR wrapper)</param> /// <param name="type">The type of this property</param> /// <param name="isInheritable">Whether the property can be inherited from the parent</param> /// <param name="parser"> /// The function used to parse the string representation of this peoperty /// (not needed for primitives, enums, string, <see cref="DateTime"/>, /// <see cref="DateTimeOffset"/> and their nullable forms) /// </param> /// <param name="defaultFactory"> /// The function used to produce the default value of this property (optional) /// </param> /// <param name="cloner"> /// The function used to clone the peoperty with /// (not needed for <see cref="ICloneable"/>s, strings, enums, all value types and their nullable forms) /// </param> /// <exception cref="ArgumentNullException">either owner, name or type is null</exception> /// <exception cref="T:System.ArgumentException">name is whitespace or already registered</exception> protected static HierarchicalProperty RegisterProperty( Type owner, string name, Type type, bool isInheritable = false, Func<string, object> parser = null, Func<object> defaultFactory = null, Func<object, object> cloner = null) { // Check for nulls. if (owner == null) throw new ArgumentNullException(nameof(owner)); if (name == null) throw new ArgumentNullException(nameof(name)); if (type == null) throw new ArgumentNullException(nameof(type)); // Make sure the name is valid. if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("name must have value. null or whitespace is not acceptable.", nameof(name)); // Add a mapping for the owner if it does not already exist. if (!RegisteredProperties.ContainsKey(owner)) RegisteredProperties.Add(owner, new Dictionary<string, HierarchicalProperty>()); // Make sure the name is not already registered. if (RegisteredProperties[owner].ContainsKey(name)) throw new ArgumentException("A property with the same name as the name provided is already registered.", nameof(name)); // Create a new instance of HierarchicalProperty for this property. var hierarchicalProperty = new HierarchicalProperty(owner, name, type, isInheritable, parser, defaultFactory, cloner); // Add the property to its owner's list of properties. RegisteredProperties[owner].Add(name, hierarchicalProperty); // Expose the instance to client code. return hierarchicalProperty; }
/// <summary> /// Bind the property to a path from the <see cref="DataContext"/> /// </summary> /// <param name="property">The property to bind</param> /// <param name="expression"> /// The expression string. /// The format of the expression must be one of the following: /// <list type="number"> /// <item> /// <term>"."</term> /// <description> /// The value will be the DataContext itself. /// </description> /// </item> /// <item> /// <term>"Foo"</term> /// <description> /// The value will be from <code>DataContext.Foo</code>. /// </description> /// </item> /// <item> /// <term>"Foo[5].Bar"</term> /// <description> /// The value will be from <code>DataContext.Foo[5].Bar</code>. /// </description> /// </item> /// <item> /// <term>"Foo.Bar["baz"].ToBaz()"</term> /// <description> /// The value will be from <code>DataContext.Foo.Bar["baz"].ToBaz()</code>. /// </description> /// </item> /// </list> /// </param> /// <param name="ignoreOnError"> /// Determines whether faulty expressions should /// throw exception or ignore. /// <remarks> /// Caution: Enabling this will also silently ignore syntactic errors. /// </remarks> /// </param> /// <exception cref="ArgumentNullException">property or expression is null</exception> /// <exception cref="T:System.InvalidOperationException">object is read only</exception> public void SetBinding(HierarchicalProperty property, string expression, bool ignoreOnError = false) { ThrowOnReadOnly(); if (property == null) throw new ArgumentNullException(nameof(property)); if (expression == null) throw new ArgumentNullException(nameof(expression)); // In case the binding is done on the data context itself change the target to the parent var target = (property == DataContextProperty) ? HierarchyParent : this; // Set the value for the property as a binding. _ownValues[property] = new Binder(target, expression, ignoreOnError); }
/// <summary> /// Set the property to this object as its own. /// </summary> /// <exception cref="ArgumentNullException">property is null</exception> /// <exception cref="T:System.InvalidOperationException">object is read only</exception> public void SetValue(HierarchicalProperty property, object value) { ThrowOnReadOnly(); if (property == null) throw new ArgumentNullException(nameof(property)); // Make sure non-nullable value-types are not assigned null. if (value == null // Make sure the property is value type... && property.Type.IsValueType // And not nullable. && Nullable.GetUnderlyingType(property.Type) == null) throw new ArgumentException("null is not assignable to " + property.Type, nameof(value)); // It is now okay to assign null to the property. But not value of the wrong type. if (value != null && !property.Type.IsInstanceOfType(value)) throw new ArgumentException("value is not an instance of " + property.Type, nameof(value)); // Add or update the value for the property. _ownValues[property] = value; }
/// <summary> /// Indicates whether this object or any ancestors has a value of the provided property. /// </summary> /// <exception cref="ArgumentNullException">property is null</exception> public bool HasValue(HierarchicalProperty property) { if (property == null) throw new ArgumentNullException(nameof(property)); // This object itself has the value. return HasOwnValue(property) // Otherwise, crawl up the tree if the property is inheritable... || (property.IsInheritable // And has a parent. && HierarchyParent != null // Recursively check the parent to determine whether // a value can be found in the hierarchy. && HierarchyParent.HasValue(property)); }
/// <summary> /// Indicates whether this object has its own value of the provided property. /// </summary> /// <exception cref="ArgumentNullException">property is null</exception> public bool HasOwnValue(HierarchicalProperty property) { if (property == null) throw new ArgumentNullException(nameof(property)); return _ownValues.ContainsKey(property); }
/// <summary> /// Get the value for the provided property. /// Returns the property's default if none exist. /// </summary> /// <exception cref="ArgumentNullException">property is null</exception> /// <exception cref="BindingExceprtion">expression might have syntax error</exception> public object GetValue(HierarchicalProperty property) { if (property == null) throw new ArgumentNullException(nameof(property)); // This object itself has the value. if (HasOwnValue(property)) { var value = _ownValues[property]; // Check for binding. var binder = value as Binder; // The property is not bound. return the value. if (binder == null) return value; // Apply binding var result = binder.Value; // Since binding is done after the context is set // type checking must be done on evaluation. var type = property.Type; // If the result of binding is null it means either // the DataContext is null or the binding expression evaluated to null. // Either way the property must be its type's default. if (result == null) return type.IsValueType ? Activator.CreateInstance(type) : null; // If the result is an instance of the property type return the result if (type.IsInstanceOfType(result)) return result; try { // Otherwise try for conversion. return Convert.ChangeType(result, type); } catch (Exception) { throw new BindingTypeMismatchExceprtion( "The type of the result from evaluating the binding expression does not match the expected type", null, this, binder.Expression, result.GetType(), type); } } // Return the property's default value if inheritance // is not allowed or there are no parents. if (!property.IsInheritable || HierarchyParent == null) return property.Default; // Recursively query the parent for the value. return HierarchyParent.GetValue(property); }
static HierarchicalObjectImp() { // register the Foo property FooProperty = RegisterProperty(typeof(HierarchicalObjectImp), nameof(Foo), typeof(int), true); // register the Bar property BarProperty = RegisterProperty(typeof(HierarchicalObjectImp), nameof(Bar), typeof(object), true); }
/// <summary> /// Initializes a new instance of the <see cref="PropertyException"/> class. /// </summary> internal PropertyException(string message, Exception innerException, HierarchicalProperty targetProperty) : base(message, innerException) { TargetProperty = targetProperty; }