Browse Source

Created PerspexPropertyRegistry.

And move registration of properties, finding properties etc here.
Steven Kirk 10 years ago
parent
commit
952e4a99f7

+ 2 - 2
src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs

@@ -88,7 +88,7 @@ namespace Perspex.Markup.Xaml.Context
                 if (attached == null)
                 {
                     propertyName = _xamlMember.Name;
-                    property = perspexObject.GetRegisteredProperties()
+                    property = PerspexPropertyRegistry.Instance.GetRegistered(perspexObject)
                         .FirstOrDefault(x => x.Name == propertyName);
                 }
                 else
@@ -98,7 +98,7 @@ namespace Perspex.Markup.Xaml.Context
 
                     propertyName = attached.DeclaringType.UnderlyingType.Name + '.' + _xamlMember.Name;
 
-                    property = perspexObject.GetRegisteredProperties()
+                    property = PerspexPropertyRegistry.Instance.GetRegistered(perspexObject)
                         .Where(x => x.IsAttached && x.OwnerType == attached.DeclaringType.UnderlyingType)
                         .FirstOrDefault(x => x.Name == _xamlMember.Name);
                 }

+ 2 - 2
src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs

@@ -46,12 +46,12 @@ namespace Perspex.Markup.Xaml.Converters
             }
 
             // First look for non-attached property on the type and then look for an attached property.
-            var property = PerspexObject.GetRegisteredProperties(type)
+            var property = PerspexPropertyRegistry.Instance.GetRegistered(type)
                 .FirstOrDefault(x => x.Name == propertyName);
 
             if (property == null)
             {
-                property = PerspexObject.GetAttachedProperties(type)
+                property = PerspexPropertyRegistry.Instance.GetAttached(type)
                     .FirstOrDefault(x => x.Name == propertyName);
             }
 

+ 1 - 0
src/Perspex.Base/Perspex.Base.csproj

@@ -50,6 +50,7 @@
     <Compile Include="PerspexLocator.cs" />
     <Compile Include="Metadata\XmlnsDefinitionAttribute.cs" />
     <Compile Include="PerspexObjectExtensions.cs" />
+    <Compile Include="PerspexPropertyRegistry.cs" />
     <Compile Include="PerspexProperty`1.cs" />
     <Compile Include="Platform\IPclPlatformWrapper.cs" />
     <Compile Include="BindingPriority.cs" />

+ 12 - 160
src/Perspex.Base/PerspexObject.cs

@@ -25,18 +25,6 @@ namespace Perspex
     /// </remarks>
     public class PerspexObject : IObservablePropertyBag, INotifyPropertyChanged
     {
-        /// <summary>
-        /// The registered properties by type.
-        /// </summary>
-        private static readonly Dictionary<Type, List<PerspexProperty>> s_registered =
-            new Dictionary<Type, List<PerspexProperty>>();
-
-        /// <summary>
-        /// The registered attached properties by owner type.
-        /// </summary>
-        private static readonly Dictionary<Type, List<PerspexProperty>> s_attached =
-            new Dictionary<Type, List<PerspexProperty>>();
-
         /// <summary>
         /// The parent object that inherited values are inherited from.
         /// </summary>
@@ -70,7 +58,7 @@ namespace Perspex
                 new PropertyEnricher("Id", GetHashCode()),
             });
 
-            foreach (var property in GetRegisteredProperties())
+            foreach (var property in PerspexPropertyRegistry.Instance.GetRegistered(this))
             {
                 object value = property.IsDirect ? 
                     property.Getter(this) : 
@@ -129,7 +117,7 @@ namespace Perspex
                         _inheritanceParent.PropertyChanged -= ParentPropertyChanged;
                     }
 
-                    var inherited = (from property in GetRegisteredProperties(GetType())
+                    var inherited = (from property in PerspexPropertyRegistry.Instance.GetRegistered(this)
                                      where property.Inherits
                                      select new
                                      {
@@ -215,92 +203,6 @@ namespace Perspex
             }
         }
 
-        /// <summary>
-        /// Gets all <see cref="PerspexProperty"/>s registered on a type.
-        /// </summary>
-        /// <param name="type">The type.</param>
-        /// <returns>A collection of <see cref="PerspexProperty"/> definitions.</returns>
-        public static IEnumerable<PerspexProperty> GetRegisteredProperties(Type type)
-        {
-            Contract.Requires<ArgumentNullException>(type != null);
-
-            TypeInfo i = type.GetTypeInfo();
-
-            while (type != null)
-            {
-                List<PerspexProperty> list;
-
-                if (s_registered.TryGetValue(type, out list))
-                {
-                    foreach (PerspexProperty p in list)
-                    {
-                        yield return p;
-                    }
-                }
-
-                type = type.GetTypeInfo().BaseType;
-            }
-        }
-
-        /// <summary>
-        /// Gets all attached <see cref="PerspexProperty"/>s registered by an owner.
-        /// </summary>
-        /// <param name="ownerType">The owner type.</param>
-        /// <returns>A collection of <see cref="PerspexProperty"/> definitions.</returns>
-        public static IEnumerable<PerspexProperty> GetAttachedProperties(Type ownerType)
-        {
-            List<PerspexProperty> list;
-
-            if (s_attached.TryGetValue(ownerType, out list))
-            {
-                return list;
-            }
-
-            return Enumerable.Empty<PerspexProperty>();
-        }
-
-        /// <summary>
-        /// Registers a <see cref="PerspexProperty"/> on a type.
-        /// </summary>
-        /// <param name="type">The type.</param>
-        /// <param name="property">The property.</param>
-        /// <remarks>
-        /// You won't usually want to call this method directly, instead use the
-        /// <see cref="PerspexProperty.Register"/> method.
-        /// </remarks>
-        public static void Register(Type type, PerspexProperty property)
-        {
-            Contract.Requires<ArgumentNullException>(type != null);
-            Contract.Requires<ArgumentNullException>(property != null);
-
-            List<PerspexProperty> list;
-
-            if (!s_registered.TryGetValue(type, out list))
-            {
-                list = new List<PerspexProperty>();
-                s_registered.Add(type, list);
-            }
-
-            if (!list.Contains(property))
-            {
-                list.Add(property);
-            }
-
-            if (property.IsAttached)
-            {
-                if (!s_attached.TryGetValue(property.OwnerType, out list))
-                {
-                    list = new List<PerspexProperty>();
-                    s_attached.Add(property.OwnerType, list);
-                }
-
-                if (!list.Contains(property))
-                {
-                    list.Add(property);
-                }
-            }
-        }
-
         public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();
 
         public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess();
@@ -409,7 +311,7 @@ namespace Perspex
                 object result = PerspexProperty.UnsetValue;
                 PriorityValue value;
 
-                if (!IsRegistered(property))
+                if (!PerspexPropertyRegistry.Instance.IsRegistered(this, property))
                 {
                     ThrowNotRegistered(property);
                 }
@@ -448,17 +350,6 @@ namespace Perspex
             }
         }
 
-        /// <summary>
-        /// Gets all properties that are registered on this object.
-        /// </summary>
-        /// <returns>
-        /// A collection of <see cref="PerspexProperty"/> objects.
-        /// </returns>
-        public IEnumerable<PerspexProperty> GetRegisteredProperties()
-        {
-            return GetRegisteredProperties(GetType());
-        }
-
         /// <summary>
         /// Checks whether a <see cref="PerspexProperty"/> is set on this object.
         /// </summary>
@@ -478,16 +369,6 @@ namespace Perspex
             return false;
         }
 
-        /// <summary>
-        /// Checks whether a <see cref="PerspexProperty"/> is registered on this class.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <returns>True if the property is registered, otherwise false.</returns>
-        public bool IsRegistered(PerspexProperty property)
-        {
-            return FindRegistered(property) != null;
-        }
-
         /// <summary>
         /// Sets a <see cref="PerspexProperty"/> value.
         /// </summary>
@@ -519,7 +400,7 @@ namespace Perspex
                 PriorityValue v;
                 var originalValue = value;
 
-                if (!IsRegistered(property))
+                if (!PerspexPropertyRegistry.Instance.IsRegistered(this, property))
                 {
                     ThrowNotRegistered(property);
                 }
@@ -620,7 +501,7 @@ namespace Perspex
             {
                 PriorityValue v;
 
-                if (!IsRegistered(property))
+                if (!PerspexPropertyRegistry.Instance.IsRegistered(this, property))
                 {
                     ThrowNotRegistered(property);
                 }
@@ -751,6 +632,12 @@ namespace Perspex
             }
         }
 
+        /// <inheritdoc/>
+        bool IPropertyBag.IsRegistered(PerspexProperty property)
+        {
+            return PerspexPropertyRegistry.Instance.IsRegistered(this, property);
+        }
+
         /// <summary>
         /// Gets all priority values set on the object.
         /// </summary>
@@ -936,41 +823,6 @@ namespace Perspex
             }
         }
 
-        /// <summary>
-        /// Given a <see cref="PerspexProperty"/> returns a registered perspex property that is
-        /// equal.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <returns>The registered property or null if not found.</returns>
-        /// <remarks>
-        /// Calling AddOwner on a direct PerspexProperty creates new new PerspexProperty with
-        /// an overridden getter and setter. This property is a different object but is equal
-        /// according to <see cref="object.Equals(object)"/>.
-        /// </remarks>
-        public PerspexProperty FindRegistered(PerspexProperty property)
-        {
-            Type type = GetType();
-
-            while (type != null)
-            {
-                List<PerspexProperty> list;
-
-                if (s_registered.TryGetValue(type, out list))
-                {
-                    var index = list.IndexOf(property);
-
-                    if (index != -1)
-                    {
-                        return list[index];
-                    }
-                }
-
-                type = type.GetTypeInfo().BaseType;
-            }
-
-            return null;
-        }
-
         /// <summary>
         /// Given a <see cref="PerspexProperty"/> returns a registered perspex property that is
         /// equal or throws if not found.
@@ -979,7 +831,7 @@ namespace Perspex
         /// <returns>The registered property.</returns>
         public PerspexProperty GetRegistered(PerspexProperty property)
         {
-            var result = FindRegistered(property);
+            var result = PerspexPropertyRegistry.Instance.FindRegistered(this, property);
 
             if (result == null)
             {

+ 5 - 4
src/Perspex.Base/PerspexProperty.cs

@@ -3,6 +3,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using System.Reactive.Subjects;
 using System.Reflection;
 using Perspex.Utilities;
@@ -367,7 +368,7 @@ namespace Perspex
                 notifying,
                 false);
 
-            PerspexObject.Register(typeof(TOwner), result);
+            PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
 
             return result;
         }
@@ -395,7 +396,7 @@ namespace Perspex
                 Cast(getter),
                 Cast(setter));
 
-            PerspexObject.Register(typeof(TOwner), result);
+            PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
 
             return result;
         }
@@ -431,7 +432,7 @@ namespace Perspex
                 null,
                 true);
 
-            PerspexObject.Register(typeof(THost), result);
+            PerspexPropertyRegistry.Instance.Register(typeof(THost), result);
 
             return result;
         }
@@ -468,7 +469,7 @@ namespace Perspex
                 null,
                 true);
 
-            PerspexObject.Register(typeof(THost), result);
+            PerspexPropertyRegistry.Instance.Register(typeof(THost), result);
 
             return result;
         }

+ 244 - 0
src/Perspex.Base/PerspexPropertyRegistry.cs

@@ -0,0 +1,244 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Perspex
+{
+    /// <summary>
+    /// Tracks registered <see cref="PerspexProperty"/> instances.
+    /// </summary>
+    public class PerspexPropertyRegistry
+    {
+        /// <summary>
+        /// The registered properties by type.
+        /// </summary>
+        private readonly Dictionary<Type, List<PerspexProperty>> _registered =
+            new Dictionary<Type, List<PerspexProperty>>();
+
+        /// <summary>
+        /// The registered attached properties by owner type.
+        /// </summary>
+        private readonly Dictionary<Type, List<PerspexProperty>> _attached =
+            new Dictionary<Type, List<PerspexProperty>>();
+
+        /// <summary>
+        /// Gets the <see cref="PerspexPropertyRegistry"/> instance
+        /// </summary>
+        public static PerspexPropertyRegistry Instance { get; }
+            = new PerspexPropertyRegistry();
+
+        /// <summary>
+        /// Gets all attached <see cref="PerspexProperty"/>s registered by an owner.
+        /// </summary>
+        /// <param name="ownerType">The owner type.</param>
+        /// <returns>A collection of <see cref="PerspexProperty"/> definitions.</returns>
+        public IEnumerable<PerspexProperty> GetAttached(Type ownerType)
+        {
+            List<PerspexProperty> list;
+
+            if (_attached.TryGetValue(ownerType, out list))
+            {
+                return list;
+            }
+
+            return Enumerable.Empty<PerspexProperty>();
+        }
+
+        /// <summary>
+        /// Gets all <see cref="PerspexProperty"/>s registered on a type.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <returns>A collection of <see cref="PerspexProperty"/> definitions.</returns>
+        public IEnumerable<PerspexProperty> GetRegistered(Type type)
+        {
+            Contract.Requires<ArgumentNullException>(type != null);
+
+            var i = type.GetTypeInfo();
+
+            while (type != null)
+            {
+                List<PerspexProperty> list;
+
+                if (_registered.TryGetValue(type, out list))
+                {
+                    foreach (PerspexProperty p in list)
+                    {
+                        yield return p;
+                    }
+                }
+
+                type = type.GetTypeInfo().BaseType;
+            }
+        }
+
+        /// <summary>
+        /// Gets all <see cref="PerspexProperty"/>s registered on a object.
+        /// </summary>
+        /// <param name="o">The object.</param>
+        /// <returns>A collection of <see cref="PerspexProperty"/> definitions.</returns>
+        public IEnumerable<PerspexProperty> GetRegistered(PerspexObject o)
+        {
+            Contract.Requires<ArgumentNullException>(o != null);
+
+            return GetRegistered(o.GetType());
+        }
+
+        /// <summary>
+        /// Finds <see cref="PerspexProperty"/> registered on a type.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <param name="property">The property.</param>
+        /// <returns>The registered property or null if not found.</returns>
+        /// <remarks>
+        /// Calling AddOwner on a PerspexProperty creates a new PerspexProperty that is a 
+        /// different object but is equal according to <see cref="object.Equals(object)"/>.
+        /// </remarks>
+        public PerspexProperty FindRegistered(Type type, PerspexProperty property)
+        {
+            while (type != null)
+            {
+                List<PerspexProperty> list;
+
+                if (_registered.TryGetValue(type, out list))
+                {
+                    var index = list.IndexOf(property);
+
+                    if (index != -1)
+                    {
+                        return list[index];
+                    }
+                }
+
+                type = type.GetTypeInfo().BaseType;
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Finds <see cref="PerspexProperty"/> registered on an object.
+        /// </summary>
+        /// <param name="o">The object.</param>
+        /// <param name="property">The property.</param>
+        /// <returns>The registered property or null if not found.</returns>
+        /// <remarks>
+        /// Calling AddOwner on a PerspexProperty creates a new PerspexProperty that is a 
+        /// different object but is equal according to <see cref="object.Equals(object)"/>.
+        /// </remarks>
+        public PerspexProperty FindRegistered(object o, PerspexProperty property)
+        {
+            return FindRegistered(o.GetType(), property);
+        }
+
+        /// <summary>
+        /// Finds a registered property on a type by name.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <param name="name">
+        /// The property name. If an attached property it should be in the form 
+        /// "OwnerType.PropertyName".
+        /// </param>
+        /// <returns>
+        /// The registered property or null if no matching property found.
+        /// </returns>
+        public PerspexProperty FindRegistered(Type type, string name)
+        {
+            Contract.Requires<ArgumentNullException>(type != null);
+            Contract.Requires<ArgumentNullException>(name != null);
+
+            var parts = name.Split('.');
+
+            if (parts.Length < 1 || parts.Length > 2)
+            {
+                throw new ArgumentException("Invalid property name.");
+            }
+
+            if (parts.Length == 1)
+            {
+                var result = GetRegistered(type)
+                    .FirstOrDefault(x => !x.IsAttached && x.Name == parts[0]);
+
+                if (result != null)
+                {
+                    return result;
+                }
+
+                // A type can .AddOwner an attached property.
+                return GetRegistered(type)
+                    .FirstOrDefault(x => x.Name == parts[0]);
+            }
+            else
+            {
+                return GetRegistered(type)
+                    .FirstOrDefault(x => x.IsAttached && x.OwnerType.Name == parts[0] && x.Name == parts[1]);
+            }
+        }
+        /// <summary>
+        /// Checks whether a <see cref="PerspexProperty"/> is registered on a type.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <param name="property">The property.</param>
+        /// <returns>True if the property is registered, otherwise false.</returns>
+        public bool IsRegistered(Type type, PerspexProperty property)
+        {
+            return FindRegistered(type, property) != null;
+        }
+
+        /// <summary>
+        /// Checks whether a <see cref="PerspexProperty"/> is registered on a object.
+        /// </summary>
+        /// <param name="o">The object.</param>
+        /// <param name="property">The property.</param>
+        /// <returns>True if the property is registered, otherwise false.</returns>
+        public bool IsRegistered(object o, PerspexProperty property)
+        {
+            return IsRegistered(o.GetType(), property);
+        }
+
+        /// <summary>
+        /// Registers a <see cref="PerspexProperty"/> on a type.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <param name="property">The property.</param>
+        /// <remarks>
+        /// You won't usually want to call this method directly, instead use the
+        /// <see cref="PerspexProperty.Register"/> method.
+        /// </remarks>
+        public void Register(Type type, PerspexProperty property)
+        {
+            Contract.Requires<ArgumentNullException>(type != null);
+            Contract.Requires<ArgumentNullException>(property != null);
+
+            List<PerspexProperty> list;
+
+            if (!_registered.TryGetValue(type, out list))
+            {
+                list = new List<PerspexProperty>();
+                _registered.Add(type, list);
+            }
+
+            if (!list.Contains(property))
+            {
+                list.Add(property);
+            }
+
+            if (property.IsAttached)
+            {
+                if (!_attached.TryGetValue(property.OwnerType, out list))
+                {
+                    list = new List<PerspexProperty>();
+                    _attached.Add(property.OwnerType, list);
+                }
+
+                if (!list.Contains(property))
+                {
+                    list.Add(property);
+                }
+            }
+        }
+    }
+}

+ 7 - 3
src/Perspex.Base/PerspexProperty`1.cs

@@ -105,7 +105,7 @@ namespace Perspex
                     "You must provide a new getter and setter when calling AddOwner on a direct PerspexProperty.");
             }
 
-            PerspexObject.Register(typeof(TOwner), this);
+            PerspexPropertyRegistry.Instance.Register(typeof(TOwner), this);
             return this;
         }
 
@@ -119,8 +119,12 @@ namespace Perspex
             Action<TOwner, TValue> setter = null)
                 where TOwner : PerspexObject
         {
-            var result = new PerspexProperty<TValue>(this, CastReturn(getter), CastParam1(setter));
-            PerspexObject.Register(typeof(TOwner), result);
+            var result = new PerspexProperty<TValue>(
+                this, 
+                CastReturn(getter), 
+                CastParam1(setter));
+
+            PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result);
             return result;
         }
 

+ 1 - 1
src/Perspex.Diagnostics/Debug.cs

@@ -37,7 +37,7 @@ namespace Perspex.Diagnostics
                 builder.Append(" ");
                 builder.AppendLine(control.Classes.ToString());
 
-                foreach (var property in control.GetRegisteredProperties())
+                foreach (var property in PerspexPropertyRegistry.Instance.GetRegistered(control))
                 {
                     var value = control.GetDiagnostic(property);
 

+ 1 - 1
src/Perspex.Diagnostics/ViewModels/ControlDetailsViewModel.cs

@@ -14,7 +14,7 @@ namespace Perspex.Diagnostics.ViewModels
         {
             if (control != null)
             {
-                Properties = control.GetRegisteredProperties()
+                Properties = PerspexPropertyRegistry.Instance.GetRegistered(control)
                     .Select(x => new PropertyDetails(control, x))
                     .OrderBy(x => x.IsAttached)
                     .ThenBy(x => x.Name);

+ 1 - 0
tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj

@@ -75,6 +75,7 @@
     <Compile Include="Collections\PropertyChangedTracker.cs" />
     <Compile Include="PerspexObjectTests_Direct.cs" />
     <Compile Include="PerspexObjectTests_GetObservable.cs" />
+    <Compile Include="PerspexPropertyRegistryTests.cs" />
     <Compile Include="PerspexObjectTests_Validation.cs" />
     <Compile Include="PerspexObjectTests_Binding.cs" />
     <Compile Include="PerspexObjectTests_Inheritance.cs" />

+ 0 - 24
tests/Perspex.Base.UnitTests/PerspexObjectTests_Metadata.cs

@@ -18,30 +18,6 @@ namespace Perspex.Base.UnitTests
             p = AttachedOwner.AttachedProperty;
         }
 
-        [Fact]
-        public void GetRegisteredProperties_Returns_Registered_Properties()
-        {
-            string[] names = PerspexObject.GetRegisteredProperties(typeof(Class1)).Select(x => x.Name).ToArray();
-
-            Assert.Equal(new[] { "Foo", "Baz", "Qux", "Attached" }, names);
-        }
-
-        [Fact]
-        public void GetRegisteredProperties_Returns_Registered_Properties_For_Base_Types()
-        {
-            string[] names = PerspexObject.GetRegisteredProperties(typeof(Class2)).Select(x => x.Name).ToArray();
-
-            Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux", "Attached" }, names);
-        }
-
-        [Fact]
-        public void GetAttachedProperties_Returns_Registered_Properties_For_Base_Types()
-        {
-            string[] names = PerspexObject.GetAttachedProperties(typeof(AttachedOwner)).Select(x => x.Name).ToArray();
-
-            Assert.Equal(new[] { "Attached" }, names);
-        }
-
         [Fact]
         public void IsSet_Returns_False_For_Unset_Property()
         {

+ 79 - 0
tests/Perspex.Base.UnitTests/PerspexPropertyRegistryTests.cs

@@ -0,0 +1,79 @@
+// Copyright (c) The Perspex Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System.Linq;
+using System.Reactive.Linq;
+using Xunit;
+
+namespace Perspex.Base.UnitTests
+{
+    public class PerspexPropertyRegistryTests
+    {
+        public PerspexPropertyRegistryTests()
+        {
+            // Ensure properties are registered.
+            PerspexProperty p;
+            p = Class1.FooProperty;
+            p = Class2.BarProperty;
+            p = AttachedOwner.AttachedProperty;
+        }
+
+        [Fact]
+        public void GetRegistered_Returns_Registered_Properties()
+        {
+            string[] names = PerspexPropertyRegistry.Instance.GetRegistered(typeof(Class1))
+                .Select(x => x.Name)
+                .ToArray();
+
+            Assert.Equal(new[] { "Foo", "Baz", "Qux", "Attached" }, names);
+        }
+
+        [Fact]
+        public void GetRegistered_Returns_Registered_Properties_For_Base_Types()
+        {
+            string[] names = PerspexPropertyRegistry.Instance.GetRegistered(typeof(Class2))
+                .Select(x => x.Name)
+                .ToArray();
+
+            Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux", "Attached" }, names);
+        }
+
+        [Fact]
+        public void GetAttached_Returns_Registered_Properties_For_Base_Types()
+        {
+            string[] names = PerspexPropertyRegistry.Instance.GetAttached(typeof(AttachedOwner)).Select(x => x.Name).ToArray();
+
+            Assert.Equal(new[] { "Attached" }, names);
+        }
+
+        private class Class1 : PerspexObject
+        {
+            public static readonly PerspexProperty<string> FooProperty =
+                PerspexProperty.Register<Class1, string>("Foo");
+
+            public static readonly PerspexProperty<string> BazProperty =
+                PerspexProperty.Register<Class1, string>("Baz");
+
+            public static readonly PerspexProperty<int> QuxProperty =
+                PerspexProperty.Register<Class1, int>("Qux");
+        }
+
+        private class Class2 : Class1
+        {
+            public static readonly PerspexProperty<string> BarProperty =
+                PerspexProperty.Register<Class2, string>("Bar");
+
+            public static readonly PerspexProperty<double> FlobProperty =
+                PerspexProperty.Register<Class2, double>("Flob");
+
+            public static readonly PerspexProperty<double?> FredProperty =
+                PerspexProperty.Register<Class2, double?>("Fred");
+        }
+
+        private class AttachedOwner
+        {
+            public static readonly PerspexProperty<string> AttachedProperty =
+                PerspexProperty.RegisterAttached<AttachedOwner, Class1, string>("Attached");
+        }
+    }
+}