123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- using System.Collections;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Diagnostics;
- using System.Diagnostics.CodeAnalysis;
- using System.Reflection;
- namespace System.Windows.Forms
- {
- public static class ListBindingHelper
- {
- private static Attribute[] browsableAttribute;
- private static Attribute[] BrowsableAttributeList
- {
- get
- {
- browsableAttribute ??= new Attribute[] { new BrowsableAttribute(true) };
- return browsableAttribute;
- }
- }
- public static object GetList(object list)
- {
- if (list is IListSource)
- {
- return (list as IListSource).GetList();
- }
- else
- {
- return list;
- }
- }
- public static object GetList(object dataSource, string dataMember)
- {
- //
- // The purpose of this method is to find a list, given a 'data source' object and a
- // description of some 'data member' property of that object which returns the list.
- //
- // - If the data source is not a list, we get the list by just querying for the
- // current value of that property on the data source itself.
- //
- // - If the data source is a list, we have to first pick some item from that list,
- // then query for the value of that property on the individual list item.
- //
- dataSource = GetList(dataSource);
- if (dataSource is null || dataSource is Type || string.IsNullOrEmpty(dataMember))
- {
- return dataSource;
- }
- PropertyDescriptorCollection dsProps = ListBindingHelper.GetListItemProperties(dataSource);
- PropertyDescriptor dmProp = dsProps.Find(dataMember, true);
- if (dmProp is null)
- {
- throw new ArgumentException("DataSource DataMember Prop Not Found" + dataMember);
- }
- object currentItem;
- if (dataSource is IEnumerable)
- {
- // Data source is an enumerable list, so walk to the first item
- currentItem = GetFirstItemByEnumerable(dataSource as IEnumerable);
- }
- else
- {
- // Data source is not a list, so just use the data source itself
- currentItem = dataSource;
- }
- // Query the data member property on the chosen object to get back the list
- return (currentItem is null) ? null : dmProp.GetValue(currentItem);
- }
- public static PropertyDescriptorCollection GetListItemProperties(object list)
- {
- PropertyDescriptorCollection pdc;
- if (list is null)
- {
- return new PropertyDescriptorCollection(null);
- }
- else if (list is Type)
- {
- pdc = GetListItemPropertiesByType(list as Type);
- }
- else
- {
- object target = GetList(list);
- if (target is ITypedList)
- {
- pdc = (target as ITypedList).GetItemProperties(null);
- }
- else if (target is IEnumerable)
- {
- pdc = GetListItemPropertiesByEnumerable(target as IEnumerable);
- }
- else
- {
- pdc = TypeDescriptor.GetProperties(target);
- }
- }
- return pdc;
- }
- public static PropertyDescriptorCollection GetListItemProperties(object list, PropertyDescriptor[] listAccessors)
- {
- if (listAccessors is null || listAccessors.Length == 0)
- {
- return GetListItemProperties(list);
- }
- else if (list is Type type)
- {
- return GetListItemPropertiesByType(type, listAccessors, 0);
- }
- object target = GetList(list);
- if (target is ITypedList typedList)
- {
- return typedList.GetItemProperties(listAccessors);
- }
- else if (target is IEnumerable enumerable)
- {
- return GetListItemPropertiesByEnumerable(enumerable, listAccessors, 0);
- }
- return GetListItemPropertiesByInstance(target, listAccessors, 0);
- }
- public static Type GetListItemType(object list)
- {
- if (list is null)
- {
- return null;
- }
- // special case for IListSource
- if (list is Type listAsType && typeof(IListSource).IsAssignableFrom(listAsType))
- {
- list = CreateInstanceOfType(listAsType);
- }
- list = GetList(list);
- if (list is null)
- {
- return null;
- }
- Type listType = (list is Type) ? (list as Type) : list.GetType();
- object listInstance = (list is Type) ? null : list;
- if (typeof(Array).IsAssignableFrom(listType))
- {
- return listType.GetElementType();
- }
- PropertyInfo indexer = GetTypedIndexer(listType);
- if (indexer is not null)
- {
- return indexer.PropertyType;
- }
- else if (listInstance is IEnumerable enumerable)
- {
- return GetListItemTypeByEnumerable(enumerable);
- }
- return listType;
- }
- // Create an object of the given type. Throw an exception if this fails.
- [ExcludeFromCodeCoverage]
- private static object CreateInstanceOfType(Type type)
- {
- object instancedObject = null;
- Exception instanceException = null;
- try
- {
- instancedObject = Activator.CreateInstance(type);
- }
- catch (TargetInvocationException ex)
- {
- instanceException = ex; // Default ctor threw an exception
- }
- catch (MethodAccessException ex)
- {
- instanceException = ex; // Default ctor was not public
- }
- catch (MissingMethodException ex)
- {
- instanceException = ex; // No default ctor defined
- }
- if (instanceException is not null)
- {
- throw new NotSupportedException("BindingSource Instance Error", instanceException);
- }
- return instancedObject;
- }
- public static Type GetListItemType(object dataSource, string dataMember)
- {
- // No data source
- if (dataSource is null)
- {
- return typeof(object);
- }
- // No data member - Determine item type directly from data source
- if (string.IsNullOrEmpty(dataMember))
- {
- return GetListItemType(dataSource);
- }
- // Get list item properties for this data source
- PropertyDescriptorCollection dsProps = GetListItemProperties(dataSource);
- if (dsProps is null)
- {
- return typeof(object);
- }
- // Find the property specified by the data member
- PropertyDescriptor dmProp = dsProps.Find(dataMember, true);
- if (dmProp is null || dmProp.PropertyType is ICustomTypeDescriptor)
- {
- return typeof(object);
- }
- // Determine item type from data member property
- return GetListItemType(dmProp.PropertyType);
- }
- private static PropertyDescriptorCollection GetListItemPropertiesByType(Type type, PropertyDescriptor[] listAccessors, int startIndex)
- {
- PropertyDescriptorCollection pdc = null;
- if (listAccessors[startIndex] is null)
- {
- return new PropertyDescriptorCollection(null);
- }
- Type subType = listAccessors[startIndex].PropertyType;
- // subType is the property type - which is not to be confused with the item type.
- // For example, if a class Customer has a property of type Orders[], then Given:
- // GetListItemProperties(typeof(Customer), PDForOrders)
- // PDForOrders.PropertyType will be Array (not Orders)
- //
- // If there are no more ListAccessors, then we want:
- // GetListItemProperties(PDForOrders.PropertyType) // this returns the shape of Orders not Array
- // If there are more listAccessors, then we'll call
- // GetListItemProperties(PDForOrders.PropertyType, listAccessors, startIndex++)
- startIndex += 1;
- if (startIndex >= listAccessors.Length)
- {
- // Last item, return shape of item
- pdc = GetListItemProperties(subType);
- }
- else
- {
- // Walk down the tree
- pdc = GetListItemPropertiesByType(subType, listAccessors, startIndex);
- }
- // Return descriptors
- return pdc;
- }
- private static PropertyDescriptorCollection GetListItemPropertiesByEnumerable(IEnumerable iEnumerable, PropertyDescriptor[] listAccessors, int startIndex)
- {
- PropertyDescriptorCollection pdc = null;
- object subList = null;
- // Walk down the tree - first try and get the value
- // This is tricky, because we can't do a standard GetValue - we need an instance of one of the
- // items in the list.
- //
- // For example:
- //
- // Customer has a property Orders which is of type Order[]
- //
- // Customers is a Customer[] (this is the IList)
- // Customers does not have the property "Orders" - Customer has that property
- // So we need to get the value of Customers[0]
- //
- object instance = GetFirstItemByEnumerable(iEnumerable);
- if (instance is not null)
- {
- // This calls GetValue(Customers[0], "Orders") - or Customers[0].Orders
- // If this list is non-null, it is an instance of Orders (Order[]) for the first customer
- subList = GetList(listAccessors[startIndex].GetValue(instance));
- }
- if (subList is null)
- {
- // Can't get shape by Instance, try by Type
- pdc = GetListItemPropertiesByType(listAccessors[startIndex].PropertyType, listAccessors, startIndex);
- }
- else
- {
- // We have the Instance (e.g. Orders)
- ++startIndex;
- if (subList is IEnumerable ienumerableSubList)
- {
- if (startIndex == listAccessors.Length)
- {
- // Last one, so get the shape
- pdc = GetListItemPropertiesByEnumerable(ienumerableSubList);
- }
- else
- {
- // Looks like they want more (e.g. Customers.Orders.OrderDetails)
- pdc = GetListItemPropertiesByEnumerable(ienumerableSubList, listAccessors, startIndex);
- }
- }
- else
- {
- // Not a list, so switch to a non-list based method of retrieving properties
- pdc = GetListItemPropertiesByInstance(subList, listAccessors, startIndex);
- }
- }
- return pdc;
- }
- private static Type GetListItemTypeByEnumerable(IEnumerable iEnumerable)
- {
- object instance = GetFirstItemByEnumerable(iEnumerable);
- return (instance is not null) ? instance.GetType() : typeof(object);
- }
- private static PropertyDescriptorCollection GetListItemPropertiesByInstance(object target, PropertyDescriptor[] listAccessors, int startIndex)
- {
- Debug.Assert(listAccessors is not null);
- // At this point, things can be simplified because:
- // We know target is _not_ a list
- // We have an instance
- if (listAccessors.Length > startIndex)
- {
- if (listAccessors[startIndex] is null)
- {
- return new PropertyDescriptorCollection(null);
- }
- // Get the value (e.g. given Foo with property Bar, this gets Foo.Bar)
- object value = listAccessors[startIndex].GetValue(target);
- if (value is null)
- {
- // It's null - we can't walk down by Instance so use Type
- return GetListItemPropertiesByType(listAccessors[startIndex].PropertyType, listAccessors, startIndex);
- }
- else
- {
- PropertyDescriptor[] accessors = null;
- if (listAccessors.Length > startIndex + 1)
- {
- int accessorsCount = listAccessors.Length - (startIndex + 1);
- accessors = new PropertyDescriptor[accessorsCount];
- for (int i = 0; i < accessorsCount; ++i)
- {
- accessors[i] = listAccessors[startIndex + 1 + i];
- }
- }
- // We've got the instance of Bar - now get it's shape
- return GetListItemProperties(value, accessors);
- }
- }
- return TypeDescriptor.GetProperties(target, BrowsableAttributeList);
- }
- // returns true if 'type' can be treated as a list
- private static bool IsListBasedType(Type type)
- {
- // check for IList, ITypedList, IListSource
- if (typeof(IList).IsAssignableFrom(type) ||
- typeof(ITypedList).IsAssignableFrom(type) ||
- typeof(IListSource).IsAssignableFrom(type))
- {
- return true;
- }
- // check for IList<>:
- if (type.IsGenericType && !type.IsGenericTypeDefinition)
- {
- if (typeof(IList<>).IsAssignableFrom(type.GetGenericTypeDefinition()))
- {
- return true;
- }
- }
- // check for SomeObject<T> : IList<T> / SomeObject : IList<(SpecificListObjectType)>
- foreach (Type curInterface in type.GetInterfaces())
- {
- if (curInterface.IsGenericType)
- {
- if (typeof(IList<>).IsAssignableFrom(curInterface.GetGenericTypeDefinition()))
- {
- return true;
- }
- }
- }
- return false;
- }
- /// <summary>
- ///
- /// Returns info about the 'indexer' property on the specified type. The presence of an indexer is used to
- /// determine that the type represents a collection or list. The return type of that indexer is used to
- /// determine the underlying item type.
- ///
- /// PROCESS: We look for the first public instance property on the type that is an 'indexer'. This property
- /// is usually - but not always - called "Item". So we look at 'indexer parameters' to identify true indexers,
- /// rather than looking at the property name. And we also ignore any indexers that return an item type of just
- /// Object, since we are trying to use indexers here to determine the actual underlying item type!
- ///
- /// NOTE: A special rule is also enforced here - we only want to consider using the typed indexer on list
- /// based types, ie. types we already know are supposed to be treated as lists (rather than list items).
- /// </summary>
- private static PropertyInfo GetTypedIndexer(Type type)
- {
- PropertyInfo indexer = null;
- if (!IsListBasedType(type))
- {
- return null;
- }
- PropertyInfo[] props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
- for (int idx = 0; idx < props.Length; idx++)
- {
- if (props[idx].GetIndexParameters().Length > 0 && props[idx].PropertyType != typeof(object))
- {
- indexer = props[idx];
- //Prefer the standard indexer, if there is one
- if (indexer.Name == "Item")
- {
- break;
- }
- }
- }
- return indexer;
- }
- private static PropertyDescriptorCollection GetListItemPropertiesByType(Type type)
- {
- return TypeDescriptor.GetProperties(GetListItemType(type), BrowsableAttributeList);
- }
- private static PropertyDescriptorCollection GetListItemPropertiesByEnumerable(IEnumerable enumerable)
- {
- PropertyDescriptorCollection pdc = null;
- Type targetType = enumerable.GetType();
- if (typeof(Array).IsAssignableFrom(targetType))
- {
- pdc = TypeDescriptor.GetProperties(targetType.GetElementType(), BrowsableAttributeList);
- }
- else
- {
- if (enumerable is ITypedList typedListEnumerable)
- {
- pdc = typedListEnumerable.GetItemProperties(null);
- }
- else
- {
- PropertyInfo indexer = GetTypedIndexer(targetType);
- if (indexer is not null && !typeof(ICustomTypeDescriptor).IsAssignableFrom(indexer.PropertyType))
- {
- Type type = indexer.PropertyType;
- pdc = TypeDescriptor.GetProperties(type, BrowsableAttributeList);
- // Reflection, and consequently TypeDescriptor would not return properties defined on the "base" interface,
- // for example
- // public interface IPerson {String FirstName { get; set; }}
- // public interface ITeacher : IPerson {int ClassRoom { get; set; }}
- // typeof (ITeacher).GetProperties() would return only the "ClassRoom" property
- // if (type.IsInterface) {
- // Type[] interfaces = type.GetInterfaces();
- // // initialize the list to an arbitrary length greater than pdc.Count
- // List<PropertyDescriptor> merged = new List<PropertyDescriptor>(pdc.Count * 2 + 1);
- // foreach (Type baseInterface in interfaces) {
- // PropertyDescriptorCollection props = TypeDescriptor.GetProperties(baseInterface, BrowsableAttributeList);
- // if (props is not null) {
- // foreach (PropertyDescriptor p in props) {
- // merged.Add(p);
- // }
- // }
- // }
- // if (merged.Count != 0) {
- // PropertyDescriptor[] props = new PropertyDescriptor[pdc.Count];
- // pdc.CopyTo(props, 0);
- // merged.AddRange(props);
- // pdc = new PropertyDescriptorCollection(merged.ToArray());
- // }
- // }
- }
- }
- }
- // See if we were successful - if not, return the shape of the first
- // item in the list
- if (pdc is null)
- {
- object instance = GetFirstItemByEnumerable(enumerable);
- if (enumerable is string)
- {
- pdc = TypeDescriptor.GetProperties(enumerable, BrowsableAttributeList);
- }
- else if (instance is null)
- {
- pdc = new PropertyDescriptorCollection(null);
- }
- else
- {
- pdc = TypeDescriptor.GetProperties(instance, BrowsableAttributeList);
- if (!(enumerable is IList) && pdc.Count == 0)
- {
- pdc = TypeDescriptor.GetProperties(enumerable, BrowsableAttributeList);
- }
- }
- }
- // Return results
- return pdc;
- }
- private static object GetFirstItemByEnumerable(IEnumerable enumerable)
- {
- object instance = null;
- if (enumerable is IList)
- {
- // If the list supports IList (which is a superset of IEnumerable), then try to use its IList indexer
- // to get the first item, since some ILists don't support use of their plain IEnumerable interface.
- IList list = enumerable as IList;
- instance = (list.Count > 0) ? list[0] : null;
- }
- else
- {
- // Otherwise use the enumerator to get the first item...
- try
- {
- IEnumerator listEnumerator = enumerable.GetEnumerator();
- if (listEnumerator is null)
- {
- return null;
- }
- listEnumerator.Reset();
- if (listEnumerator.MoveNext())
- {
- instance = listEnumerator.Current;
- }
- // after we are done w/ the enumerator, reset it
- listEnumerator.Reset();
- }
- catch (NotSupportedException)
- {
- // Some data sources do not offer a full implementation of IEnumerable. For example, SqlDataReader
- // only supports reading forwards through items, so it does not support calls to IEnumerable.Reset().
- instance = null;
- }
- }
- return instance;
- }
- }
- }
|