AvaloniaPropertyRegistry.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Runtime.CompilerServices;
  5. namespace Avalonia
  6. {
  7. /// <summary>
  8. /// Tracks registered <see cref="AvaloniaProperty"/> instances.
  9. /// </summary>
  10. public class AvaloniaPropertyRegistry
  11. {
  12. private readonly Dictionary<int, AvaloniaProperty> _properties =
  13. new Dictionary<int, AvaloniaProperty>();
  14. private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registered =
  15. new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
  16. private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _attached =
  17. new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
  18. private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _direct =
  19. new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
  20. private readonly Dictionary<Type, List<AvaloniaProperty>> _registeredCache =
  21. new Dictionary<Type, List<AvaloniaProperty>>();
  22. private readonly Dictionary<Type, List<AvaloniaProperty>> _attachedCache =
  23. new Dictionary<Type, List<AvaloniaProperty>>();
  24. private readonly Dictionary<Type, List<AvaloniaProperty>> _directCache =
  25. new Dictionary<Type, List<AvaloniaProperty>>();
  26. private readonly Dictionary<Type, List<AvaloniaProperty>> _inheritedCache =
  27. new Dictionary<Type, List<AvaloniaProperty>>();
  28. /// <summary>
  29. /// Gets the <see cref="AvaloniaPropertyRegistry"/> instance
  30. /// </summary>
  31. public static AvaloniaPropertyRegistry Instance { get; }
  32. = new AvaloniaPropertyRegistry();
  33. /// <summary>
  34. /// Gets a list of all registered properties.
  35. /// </summary>
  36. internal IReadOnlyCollection<AvaloniaProperty> Properties => _properties.Values;
  37. /// <summary>
  38. /// Gets all non-attached <see cref="AvaloniaProperty"/>s registered on a type.
  39. /// </summary>
  40. /// <param name="type">The type.</param>
  41. /// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
  42. [UnconditionalSuppressMessage("Trimming", "IL2059", Justification = "If type was trimmed out, no properties were referenced")]
  43. public IReadOnlyList<AvaloniaProperty> GetRegistered(Type type)
  44. {
  45. _ = type ?? throw new ArgumentNullException(nameof(type));
  46. if (_registeredCache.TryGetValue(type, out var result))
  47. {
  48. return result;
  49. }
  50. var t = type;
  51. result = new List<AvaloniaProperty>();
  52. while (t != null)
  53. {
  54. // Ensure the type's static ctor has been run.
  55. RuntimeHelpers.RunClassConstructor(t.TypeHandle);
  56. if (_registered.TryGetValue(t, out var registered))
  57. {
  58. result.AddRange(registered.Values);
  59. }
  60. t = t.BaseType;
  61. }
  62. _registeredCache.Add(type, result);
  63. return result;
  64. }
  65. /// <summary>
  66. /// Gets all attached <see cref="AvaloniaProperty"/>s registered on a type.
  67. /// </summary>
  68. /// <param name="type">The type.</param>
  69. /// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
  70. public IReadOnlyList<AvaloniaProperty> GetRegisteredAttached(Type type)
  71. {
  72. _ = type ?? throw new ArgumentNullException(nameof(type));
  73. if (_attachedCache.TryGetValue(type, out var result))
  74. {
  75. return result;
  76. }
  77. var t = type;
  78. result = new List<AvaloniaProperty>();
  79. while (t != null)
  80. {
  81. if (_attached.TryGetValue(t, out var attached))
  82. {
  83. result.AddRange(attached.Values);
  84. }
  85. t = t.BaseType;
  86. }
  87. _attachedCache.Add(type, result);
  88. return result;
  89. }
  90. /// <summary>
  91. /// Gets all direct <see cref="AvaloniaProperty"/>s registered on a type.
  92. /// </summary>
  93. /// <param name="type">The type.</param>
  94. /// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
  95. public IReadOnlyList<AvaloniaProperty> GetRegisteredDirect(Type type)
  96. {
  97. _ = type ?? throw new ArgumentNullException(nameof(type));
  98. if (_directCache.TryGetValue(type, out var result))
  99. {
  100. return result;
  101. }
  102. var t = type;
  103. result = new List<AvaloniaProperty>();
  104. while (t != null)
  105. {
  106. if (_direct.TryGetValue(t, out var direct))
  107. {
  108. result.AddRange(direct.Values);
  109. }
  110. t = t.BaseType;
  111. }
  112. _directCache.Add(type, result);
  113. return result;
  114. }
  115. /// <summary>
  116. /// Gets all inherited <see cref="AvaloniaProperty"/>s registered on a type.
  117. /// </summary>
  118. /// <param name="type">The type.</param>
  119. /// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
  120. public IReadOnlyList<AvaloniaProperty> GetRegisteredInherited(Type type)
  121. {
  122. _ = type ?? throw new ArgumentNullException(nameof(type));
  123. if (_inheritedCache.TryGetValue(type, out var result))
  124. {
  125. return result;
  126. }
  127. result = new List<AvaloniaProperty>();
  128. var visited = new HashSet<AvaloniaProperty>();
  129. var registered = GetRegistered(type);
  130. var registeredCount = registered.Count;
  131. for (var i = 0; i < registeredCount; i++)
  132. {
  133. var property = registered[i];
  134. if (property.Inherits)
  135. {
  136. result.Add(property);
  137. visited.Add(property);
  138. }
  139. }
  140. var registeredAttached = GetRegisteredAttached(type);
  141. var registeredAttachedCount = registeredAttached.Count;
  142. for (var i = 0; i < registeredAttachedCount; i++)
  143. {
  144. var property = registeredAttached[i];
  145. if (property.Inherits)
  146. {
  147. if (!visited.Contains(property))
  148. {
  149. result.Add(property);
  150. }
  151. }
  152. }
  153. _inheritedCache.Add(type, result);
  154. return result;
  155. }
  156. /// <summary>
  157. /// Gets all <see cref="AvaloniaProperty"/>s registered on a object.
  158. /// </summary>
  159. /// <param name="o">The object.</param>
  160. /// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
  161. public IReadOnlyList<AvaloniaProperty> GetRegistered(AvaloniaObject o)
  162. {
  163. _ = o ?? throw new ArgumentNullException(nameof(o));
  164. return GetRegistered(o.GetType());
  165. }
  166. /// <summary>
  167. /// Gets a direct property as registered on an object.
  168. /// </summary>
  169. /// <param name="o">The object.</param>
  170. /// <param name="property">The direct property.</param>
  171. /// <returns>
  172. /// The registered.
  173. /// </returns>
  174. public DirectPropertyBase<T> GetRegisteredDirect<T>(
  175. AvaloniaObject o,
  176. DirectPropertyBase<T> property)
  177. {
  178. return FindRegisteredDirect(o, property) ??
  179. throw new ArgumentException($"Property '{property.Name} not registered on '{o.GetType()}");
  180. }
  181. /// <summary>
  182. /// Finds a registered property on a type by name.
  183. /// </summary>
  184. /// <param name="type">The type.</param>
  185. /// <param name="name">The property name.</param>
  186. /// <returns>
  187. /// The registered property or null if no matching property found.
  188. /// </returns>
  189. /// <exception cref="InvalidOperationException">
  190. /// The property name contains a '.'.
  191. /// </exception>
  192. public AvaloniaProperty? FindRegistered(Type type, string name)
  193. {
  194. _ = type ?? throw new ArgumentNullException(nameof(type));
  195. _ = name ?? throw new ArgumentNullException(nameof(name));
  196. if (name.Contains('.'))
  197. {
  198. throw new InvalidOperationException("Attached properties not supported.");
  199. }
  200. var registered = GetRegistered(type);
  201. var registeredCount = registered.Count;
  202. for (var i = 0; i < registeredCount; i++)
  203. {
  204. AvaloniaProperty x = registered[i];
  205. if (x.Name == name)
  206. {
  207. return x;
  208. }
  209. }
  210. return null;
  211. }
  212. /// <summary>
  213. /// Finds a registered property on an object by name.
  214. /// </summary>
  215. /// <param name="o">The object.</param>
  216. /// <param name="name">The property name.</param>
  217. /// <returns>
  218. /// The registered property or null if no matching property found.
  219. /// </returns>
  220. /// <exception cref="InvalidOperationException">
  221. /// The property name contains a '.'.
  222. /// </exception>
  223. public AvaloniaProperty? FindRegistered(AvaloniaObject o, string name)
  224. {
  225. _ = o ?? throw new ArgumentNullException(nameof(o));
  226. _ = name ?? throw new ArgumentNullException(nameof(name));
  227. return FindRegistered(o.GetType(), name);
  228. }
  229. /// <summary>
  230. /// Finds a direct property as registered on an object.
  231. /// </summary>
  232. /// <param name="o">The object.</param>
  233. /// <param name="property">The direct property.</param>
  234. /// <returns>
  235. /// The registered property or null if no matching property found.
  236. /// </returns>
  237. public DirectPropertyBase<T>? FindRegisteredDirect<T>(
  238. AvaloniaObject o,
  239. DirectPropertyBase<T> property)
  240. {
  241. if (property.Owner == o.GetType())
  242. {
  243. return property;
  244. }
  245. var registeredDirect = GetRegisteredDirect(o.GetType());
  246. var registeredDirectCount = registeredDirect.Count;
  247. for (var i = 0; i < registeredDirectCount; i++)
  248. {
  249. var p = registeredDirect[i];
  250. if (p == property)
  251. {
  252. return (DirectPropertyBase<T>)p;
  253. }
  254. }
  255. return null;
  256. }
  257. /// <summary>
  258. /// Finds a registered property by Id.
  259. /// </summary>
  260. /// <param name="id">The property Id.</param>
  261. /// <returns>The registered property or null if no matching property found.</returns>
  262. internal AvaloniaProperty? FindRegistered(int id)
  263. {
  264. return id < _properties.Count ? _properties[id] : null;
  265. }
  266. /// <summary>
  267. /// Checks whether a <see cref="AvaloniaProperty"/> is registered on a type.
  268. /// </summary>
  269. /// <param name="type">The type.</param>
  270. /// <param name="property">The property.</param>
  271. /// <returns>True if the property is registered, otherwise false.</returns>
  272. public bool IsRegistered(Type type, AvaloniaProperty property)
  273. {
  274. _ = type ?? throw new ArgumentNullException(nameof(type));
  275. _ = property ?? throw new ArgumentNullException(nameof(property));
  276. static bool ContainsProperty(IReadOnlyList<AvaloniaProperty> properties, AvaloniaProperty property)
  277. {
  278. var propertiesCount = properties.Count;
  279. for (var i = 0; i < propertiesCount; i++)
  280. {
  281. if (properties[i] == property)
  282. {
  283. return true;
  284. }
  285. }
  286. return false;
  287. }
  288. return ContainsProperty(Instance.GetRegistered(type), property) ||
  289. ContainsProperty(Instance.GetRegisteredAttached(type), property);
  290. }
  291. /// <summary>
  292. /// Checks whether a <see cref="AvaloniaProperty"/> is registered on a object.
  293. /// </summary>
  294. /// <param name="o">The object.</param>
  295. /// <param name="property">The property.</param>
  296. /// <returns>True if the property is registered, otherwise false.</returns>
  297. public bool IsRegistered(object o, AvaloniaProperty property)
  298. {
  299. _ = o ?? throw new ArgumentNullException(nameof(o));
  300. _ = property ?? throw new ArgumentNullException(nameof(property));
  301. return IsRegistered(o.GetType(), property);
  302. }
  303. /// <summary>
  304. /// Registers a <see cref="AvaloniaProperty"/> on a type.
  305. /// </summary>
  306. /// <param name="type">The type.</param>
  307. /// <param name="property">The property.</param>
  308. /// <remarks>
  309. /// You won't usually want to call this method directly, instead use the
  310. /// <see cref="AvaloniaProperty.Register{TOwner, TValue}(string, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{AvaloniaObject, TValue, TValue}, Action{AvaloniaObject, bool})"/>
  311. /// method.
  312. /// </remarks>
  313. public void Register(Type type, AvaloniaProperty property)
  314. {
  315. _ = type ?? throw new ArgumentNullException(nameof(type));
  316. _ = property ?? throw new ArgumentNullException(nameof(property));
  317. if (!_registered.TryGetValue(type, out var inner))
  318. {
  319. inner = new Dictionary<int, AvaloniaProperty>();
  320. inner.Add(property.Id, property);
  321. _registered.Add(type, inner);
  322. }
  323. else if (!inner.ContainsKey(property.Id))
  324. {
  325. inner.Add(property.Id, property);
  326. }
  327. if (property.IsDirect)
  328. {
  329. if (!_direct.TryGetValue(type, out inner))
  330. {
  331. inner = new Dictionary<int, AvaloniaProperty>();
  332. inner.Add(property.Id, property);
  333. _direct.Add(type, inner);
  334. }
  335. else if (!inner.ContainsKey(property.Id))
  336. {
  337. inner.Add(property.Id, property);
  338. }
  339. _directCache.Clear();
  340. }
  341. if (!_properties.ContainsKey(property.Id))
  342. {
  343. _properties.Add(property.Id, property);
  344. }
  345. _registeredCache.Clear();
  346. _inheritedCache.Clear();
  347. }
  348. /// <summary>
  349. /// Registers an attached <see cref="AvaloniaProperty"/> on a type.
  350. /// </summary>
  351. /// <param name="type">The type.</param>
  352. /// <param name="property">The property.</param>
  353. /// <remarks>
  354. /// You won't usually want to call this method directly, instead use the
  355. /// <see cref="AvaloniaProperty.RegisterAttached{THost, TValue}(string, Type, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{AvaloniaObject, TValue, TValue})"/>
  356. /// method.
  357. /// </remarks>
  358. public void RegisterAttached(Type type, AvaloniaProperty property)
  359. {
  360. _ = type ?? throw new ArgumentNullException(nameof(type));
  361. _ = property ?? throw new ArgumentNullException(nameof(property));
  362. if (!property.IsAttached)
  363. {
  364. throw new InvalidOperationException(
  365. "Cannot register a non-attached property as attached.");
  366. }
  367. if (!_attached.TryGetValue(type, out var inner))
  368. {
  369. inner = new Dictionary<int, AvaloniaProperty>();
  370. inner.Add(property.Id, property);
  371. _attached.Add(type, inner);
  372. }
  373. else
  374. {
  375. inner.Add(property.Id, property);
  376. }
  377. _attachedCache.Clear();
  378. _inheritedCache.Clear();
  379. }
  380. }
  381. }