ListBindingHelper.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.ComponentModel;
  7. using System.Diagnostics;
  8. using System.Diagnostics.CodeAnalysis;
  9. using System.Reflection;
  10. namespace System.Windows.Forms
  11. {
  12. public static class ListBindingHelper
  13. {
  14. private static Attribute[] browsableAttribute;
  15. private static Attribute[] BrowsableAttributeList
  16. {
  17. get
  18. {
  19. browsableAttribute ??= new Attribute[] { new BrowsableAttribute(true) };
  20. return browsableAttribute;
  21. }
  22. }
  23. public static object GetList(object list)
  24. {
  25. if (list is IListSource)
  26. {
  27. return (list as IListSource).GetList();
  28. }
  29. else
  30. {
  31. return list;
  32. }
  33. }
  34. public static object GetList(object dataSource, string dataMember)
  35. {
  36. //
  37. // The purpose of this method is to find a list, given a 'data source' object and a
  38. // description of some 'data member' property of that object which returns the list.
  39. //
  40. // - If the data source is not a list, we get the list by just querying for the
  41. // current value of that property on the data source itself.
  42. //
  43. // - If the data source is a list, we have to first pick some item from that list,
  44. // then query for the value of that property on the individual list item.
  45. //
  46. dataSource = GetList(dataSource);
  47. if (dataSource is null || dataSource is Type || string.IsNullOrEmpty(dataMember))
  48. {
  49. return dataSource;
  50. }
  51. PropertyDescriptorCollection dsProps = ListBindingHelper.GetListItemProperties(dataSource);
  52. PropertyDescriptor dmProp = dsProps.Find(dataMember, true);
  53. if (dmProp is null)
  54. {
  55. throw new ArgumentException("DataSource DataMember Prop Not Found" + dataMember);
  56. }
  57. object currentItem;
  58. if (dataSource is IEnumerable)
  59. {
  60. // Data source is an enumerable list, so walk to the first item
  61. currentItem = GetFirstItemByEnumerable(dataSource as IEnumerable);
  62. }
  63. else
  64. {
  65. // Data source is not a list, so just use the data source itself
  66. currentItem = dataSource;
  67. }
  68. // Query the data member property on the chosen object to get back the list
  69. return (currentItem is null) ? null : dmProp.GetValue(currentItem);
  70. }
  71. public static PropertyDescriptorCollection GetListItemProperties(object list)
  72. {
  73. PropertyDescriptorCollection pdc;
  74. if (list is null)
  75. {
  76. return new PropertyDescriptorCollection(null);
  77. }
  78. else if (list is Type)
  79. {
  80. pdc = GetListItemPropertiesByType(list as Type);
  81. }
  82. else
  83. {
  84. object target = GetList(list);
  85. if (target is ITypedList)
  86. {
  87. pdc = (target as ITypedList).GetItemProperties(null);
  88. }
  89. else if (target is IEnumerable)
  90. {
  91. pdc = GetListItemPropertiesByEnumerable(target as IEnumerable);
  92. }
  93. else
  94. {
  95. pdc = TypeDescriptor.GetProperties(target);
  96. }
  97. }
  98. return pdc;
  99. }
  100. public static PropertyDescriptorCollection GetListItemProperties(object list, PropertyDescriptor[] listAccessors)
  101. {
  102. if (listAccessors is null || listAccessors.Length == 0)
  103. {
  104. return GetListItemProperties(list);
  105. }
  106. else if (list is Type type)
  107. {
  108. return GetListItemPropertiesByType(type, listAccessors, 0);
  109. }
  110. object target = GetList(list);
  111. if (target is ITypedList typedList)
  112. {
  113. return typedList.GetItemProperties(listAccessors);
  114. }
  115. else if (target is IEnumerable enumerable)
  116. {
  117. return GetListItemPropertiesByEnumerable(enumerable, listAccessors, 0);
  118. }
  119. return GetListItemPropertiesByInstance(target, listAccessors, 0);
  120. }
  121. public static Type GetListItemType(object list)
  122. {
  123. if (list is null)
  124. {
  125. return null;
  126. }
  127. // special case for IListSource
  128. if (list is Type listAsType && typeof(IListSource).IsAssignableFrom(listAsType))
  129. {
  130. list = CreateInstanceOfType(listAsType);
  131. }
  132. list = GetList(list);
  133. if (list is null)
  134. {
  135. return null;
  136. }
  137. Type listType = (list is Type) ? (list as Type) : list.GetType();
  138. object listInstance = (list is Type) ? null : list;
  139. if (typeof(Array).IsAssignableFrom(listType))
  140. {
  141. return listType.GetElementType();
  142. }
  143. PropertyInfo indexer = GetTypedIndexer(listType);
  144. if (indexer is not null)
  145. {
  146. return indexer.PropertyType;
  147. }
  148. else if (listInstance is IEnumerable enumerable)
  149. {
  150. return GetListItemTypeByEnumerable(enumerable);
  151. }
  152. return listType;
  153. }
  154. // Create an object of the given type. Throw an exception if this fails.
  155. [ExcludeFromCodeCoverage]
  156. private static object CreateInstanceOfType(Type type)
  157. {
  158. object instancedObject = null;
  159. Exception instanceException = null;
  160. try
  161. {
  162. instancedObject = Activator.CreateInstance(type);
  163. }
  164. catch (TargetInvocationException ex)
  165. {
  166. instanceException = ex; // Default ctor threw an exception
  167. }
  168. catch (MethodAccessException ex)
  169. {
  170. instanceException = ex; // Default ctor was not public
  171. }
  172. catch (MissingMethodException ex)
  173. {
  174. instanceException = ex; // No default ctor defined
  175. }
  176. if (instanceException is not null)
  177. {
  178. throw new NotSupportedException("BindingSource Instance Error", instanceException);
  179. }
  180. return instancedObject;
  181. }
  182. public static Type GetListItemType(object dataSource, string dataMember)
  183. {
  184. // No data source
  185. if (dataSource is null)
  186. {
  187. return typeof(object);
  188. }
  189. // No data member - Determine item type directly from data source
  190. if (string.IsNullOrEmpty(dataMember))
  191. {
  192. return GetListItemType(dataSource);
  193. }
  194. // Get list item properties for this data source
  195. PropertyDescriptorCollection dsProps = GetListItemProperties(dataSource);
  196. if (dsProps is null)
  197. {
  198. return typeof(object);
  199. }
  200. // Find the property specified by the data member
  201. PropertyDescriptor dmProp = dsProps.Find(dataMember, true);
  202. if (dmProp is null || dmProp.PropertyType is ICustomTypeDescriptor)
  203. {
  204. return typeof(object);
  205. }
  206. // Determine item type from data member property
  207. return GetListItemType(dmProp.PropertyType);
  208. }
  209. private static PropertyDescriptorCollection GetListItemPropertiesByType(Type type, PropertyDescriptor[] listAccessors, int startIndex)
  210. {
  211. PropertyDescriptorCollection pdc = null;
  212. if (listAccessors[startIndex] is null)
  213. {
  214. return new PropertyDescriptorCollection(null);
  215. }
  216. Type subType = listAccessors[startIndex].PropertyType;
  217. // subType is the property type - which is not to be confused with the item type.
  218. // For example, if a class Customer has a property of type Orders[], then Given:
  219. // GetListItemProperties(typeof(Customer), PDForOrders)
  220. // PDForOrders.PropertyType will be Array (not Orders)
  221. //
  222. // If there are no more ListAccessors, then we want:
  223. // GetListItemProperties(PDForOrders.PropertyType) // this returns the shape of Orders not Array
  224. // If there are more listAccessors, then we'll call
  225. // GetListItemProperties(PDForOrders.PropertyType, listAccessors, startIndex++)
  226. startIndex += 1;
  227. if (startIndex >= listAccessors.Length)
  228. {
  229. // Last item, return shape of item
  230. pdc = GetListItemProperties(subType);
  231. }
  232. else
  233. {
  234. // Walk down the tree
  235. pdc = GetListItemPropertiesByType(subType, listAccessors, startIndex);
  236. }
  237. // Return descriptors
  238. return pdc;
  239. }
  240. private static PropertyDescriptorCollection GetListItemPropertiesByEnumerable(IEnumerable iEnumerable, PropertyDescriptor[] listAccessors, int startIndex)
  241. {
  242. PropertyDescriptorCollection pdc = null;
  243. object subList = null;
  244. // Walk down the tree - first try and get the value
  245. // This is tricky, because we can't do a standard GetValue - we need an instance of one of the
  246. // items in the list.
  247. //
  248. // For example:
  249. //
  250. // Customer has a property Orders which is of type Order[]
  251. //
  252. // Customers is a Customer[] (this is the IList)
  253. // Customers does not have the property "Orders" - Customer has that property
  254. // So we need to get the value of Customers[0]
  255. //
  256. object instance = GetFirstItemByEnumerable(iEnumerable);
  257. if (instance is not null)
  258. {
  259. // This calls GetValue(Customers[0], "Orders") - or Customers[0].Orders
  260. // If this list is non-null, it is an instance of Orders (Order[]) for the first customer
  261. subList = GetList(listAccessors[startIndex].GetValue(instance));
  262. }
  263. if (subList is null)
  264. {
  265. // Can't get shape by Instance, try by Type
  266. pdc = GetListItemPropertiesByType(listAccessors[startIndex].PropertyType, listAccessors, startIndex);
  267. }
  268. else
  269. {
  270. // We have the Instance (e.g. Orders)
  271. ++startIndex;
  272. if (subList is IEnumerable ienumerableSubList)
  273. {
  274. if (startIndex == listAccessors.Length)
  275. {
  276. // Last one, so get the shape
  277. pdc = GetListItemPropertiesByEnumerable(ienumerableSubList);
  278. }
  279. else
  280. {
  281. // Looks like they want more (e.g. Customers.Orders.OrderDetails)
  282. pdc = GetListItemPropertiesByEnumerable(ienumerableSubList, listAccessors, startIndex);
  283. }
  284. }
  285. else
  286. {
  287. // Not a list, so switch to a non-list based method of retrieving properties
  288. pdc = GetListItemPropertiesByInstance(subList, listAccessors, startIndex);
  289. }
  290. }
  291. return pdc;
  292. }
  293. private static Type GetListItemTypeByEnumerable(IEnumerable iEnumerable)
  294. {
  295. object instance = GetFirstItemByEnumerable(iEnumerable);
  296. return (instance is not null) ? instance.GetType() : typeof(object);
  297. }
  298. private static PropertyDescriptorCollection GetListItemPropertiesByInstance(object target, PropertyDescriptor[] listAccessors, int startIndex)
  299. {
  300. Debug.Assert(listAccessors is not null);
  301. // At this point, things can be simplified because:
  302. // We know target is _not_ a list
  303. // We have an instance
  304. if (listAccessors.Length > startIndex)
  305. {
  306. if (listAccessors[startIndex] is null)
  307. {
  308. return new PropertyDescriptorCollection(null);
  309. }
  310. // Get the value (e.g. given Foo with property Bar, this gets Foo.Bar)
  311. object value = listAccessors[startIndex].GetValue(target);
  312. if (value is null)
  313. {
  314. // It's null - we can't walk down by Instance so use Type
  315. return GetListItemPropertiesByType(listAccessors[startIndex].PropertyType, listAccessors, startIndex);
  316. }
  317. else
  318. {
  319. PropertyDescriptor[] accessors = null;
  320. if (listAccessors.Length > startIndex + 1)
  321. {
  322. int accessorsCount = listAccessors.Length - (startIndex + 1);
  323. accessors = new PropertyDescriptor[accessorsCount];
  324. for (int i = 0; i < accessorsCount; ++i)
  325. {
  326. accessors[i] = listAccessors[startIndex + 1 + i];
  327. }
  328. }
  329. // We've got the instance of Bar - now get it's shape
  330. return GetListItemProperties(value, accessors);
  331. }
  332. }
  333. return TypeDescriptor.GetProperties(target, BrowsableAttributeList);
  334. }
  335. // returns true if 'type' can be treated as a list
  336. private static bool IsListBasedType(Type type)
  337. {
  338. // check for IList, ITypedList, IListSource
  339. if (typeof(IList).IsAssignableFrom(type) ||
  340. typeof(ITypedList).IsAssignableFrom(type) ||
  341. typeof(IListSource).IsAssignableFrom(type))
  342. {
  343. return true;
  344. }
  345. // check for IList<>:
  346. if (type.IsGenericType && !type.IsGenericTypeDefinition)
  347. {
  348. if (typeof(IList<>).IsAssignableFrom(type.GetGenericTypeDefinition()))
  349. {
  350. return true;
  351. }
  352. }
  353. // check for SomeObject<T> : IList<T> / SomeObject : IList<(SpecificListObjectType)>
  354. foreach (Type curInterface in type.GetInterfaces())
  355. {
  356. if (curInterface.IsGenericType)
  357. {
  358. if (typeof(IList<>).IsAssignableFrom(curInterface.GetGenericTypeDefinition()))
  359. {
  360. return true;
  361. }
  362. }
  363. }
  364. return false;
  365. }
  366. /// <summary>
  367. ///
  368. /// Returns info about the 'indexer' property on the specified type. The presence of an indexer is used to
  369. /// determine that the type represents a collection or list. The return type of that indexer is used to
  370. /// determine the underlying item type.
  371. ///
  372. /// PROCESS: We look for the first public instance property on the type that is an 'indexer'. This property
  373. /// is usually - but not always - called "Item". So we look at 'indexer parameters' to identify true indexers,
  374. /// rather than looking at the property name. And we also ignore any indexers that return an item type of just
  375. /// Object, since we are trying to use indexers here to determine the actual underlying item type!
  376. ///
  377. /// NOTE: A special rule is also enforced here - we only want to consider using the typed indexer on list
  378. /// based types, ie. types we already know are supposed to be treated as lists (rather than list items).
  379. /// </summary>
  380. private static PropertyInfo GetTypedIndexer(Type type)
  381. {
  382. PropertyInfo indexer = null;
  383. if (!IsListBasedType(type))
  384. {
  385. return null;
  386. }
  387. PropertyInfo[] props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
  388. for (int idx = 0; idx < props.Length; idx++)
  389. {
  390. if (props[idx].GetIndexParameters().Length > 0 && props[idx].PropertyType != typeof(object))
  391. {
  392. indexer = props[idx];
  393. //Prefer the standard indexer, if there is one
  394. if (indexer.Name == "Item")
  395. {
  396. break;
  397. }
  398. }
  399. }
  400. return indexer;
  401. }
  402. private static PropertyDescriptorCollection GetListItemPropertiesByType(Type type)
  403. {
  404. return TypeDescriptor.GetProperties(GetListItemType(type), BrowsableAttributeList);
  405. }
  406. private static PropertyDescriptorCollection GetListItemPropertiesByEnumerable(IEnumerable enumerable)
  407. {
  408. PropertyDescriptorCollection pdc = null;
  409. Type targetType = enumerable.GetType();
  410. if (typeof(Array).IsAssignableFrom(targetType))
  411. {
  412. pdc = TypeDescriptor.GetProperties(targetType.GetElementType(), BrowsableAttributeList);
  413. }
  414. else
  415. {
  416. if (enumerable is ITypedList typedListEnumerable)
  417. {
  418. pdc = typedListEnumerable.GetItemProperties(null);
  419. }
  420. else
  421. {
  422. PropertyInfo indexer = GetTypedIndexer(targetType);
  423. if (indexer is not null && !typeof(ICustomTypeDescriptor).IsAssignableFrom(indexer.PropertyType))
  424. {
  425. Type type = indexer.PropertyType;
  426. pdc = TypeDescriptor.GetProperties(type, BrowsableAttributeList);
  427. // Reflection, and consequently TypeDescriptor would not return properties defined on the "base" interface,
  428. // for example
  429. // public interface IPerson {String FirstName { get; set; }}
  430. // public interface ITeacher : IPerson {int ClassRoom { get; set; }}
  431. // typeof (ITeacher).GetProperties() would return only the "ClassRoom" property
  432. // if (type.IsInterface) {
  433. // Type[] interfaces = type.GetInterfaces();
  434. // // initialize the list to an arbitrary length greater than pdc.Count
  435. // List<PropertyDescriptor> merged = new List<PropertyDescriptor>(pdc.Count * 2 + 1);
  436. // foreach (Type baseInterface in interfaces) {
  437. // PropertyDescriptorCollection props = TypeDescriptor.GetProperties(baseInterface, BrowsableAttributeList);
  438. // if (props is not null) {
  439. // foreach (PropertyDescriptor p in props) {
  440. // merged.Add(p);
  441. // }
  442. // }
  443. // }
  444. // if (merged.Count != 0) {
  445. // PropertyDescriptor[] props = new PropertyDescriptor[pdc.Count];
  446. // pdc.CopyTo(props, 0);
  447. // merged.AddRange(props);
  448. // pdc = new PropertyDescriptorCollection(merged.ToArray());
  449. // }
  450. // }
  451. }
  452. }
  453. }
  454. // See if we were successful - if not, return the shape of the first
  455. // item in the list
  456. if (pdc is null)
  457. {
  458. object instance = GetFirstItemByEnumerable(enumerable);
  459. if (enumerable is string)
  460. {
  461. pdc = TypeDescriptor.GetProperties(enumerable, BrowsableAttributeList);
  462. }
  463. else if (instance is null)
  464. {
  465. pdc = new PropertyDescriptorCollection(null);
  466. }
  467. else
  468. {
  469. pdc = TypeDescriptor.GetProperties(instance, BrowsableAttributeList);
  470. if (!(enumerable is IList) && pdc.Count == 0)
  471. {
  472. pdc = TypeDescriptor.GetProperties(enumerable, BrowsableAttributeList);
  473. }
  474. }
  475. }
  476. // Return results
  477. return pdc;
  478. }
  479. private static object GetFirstItemByEnumerable(IEnumerable enumerable)
  480. {
  481. object instance = null;
  482. if (enumerable is IList)
  483. {
  484. // If the list supports IList (which is a superset of IEnumerable), then try to use its IList indexer
  485. // to get the first item, since some ILists don't support use of their plain IEnumerable interface.
  486. IList list = enumerable as IList;
  487. instance = (list.Count > 0) ? list[0] : null;
  488. }
  489. else
  490. {
  491. // Otherwise use the enumerator to get the first item...
  492. try
  493. {
  494. IEnumerator listEnumerator = enumerable.GetEnumerator();
  495. if (listEnumerator is null)
  496. {
  497. return null;
  498. }
  499. listEnumerator.Reset();
  500. if (listEnumerator.MoveNext())
  501. {
  502. instance = listEnumerator.Current;
  503. }
  504. // after we are done w/ the enumerator, reset it
  505. listEnumerator.Reset();
  506. }
  507. catch (NotSupportedException)
  508. {
  509. // Some data sources do not offer a full implementation of IEnumerable. For example, SqlDataReader
  510. // only supports reading forwards through items, so it does not support calls to IEnumerable.Reset().
  511. instance = null;
  512. }
  513. }
  514. return instance;
  515. }
  516. }
  517. }