| File: System\ComponentModel\AttributeCollection.cs | Web Access |
| Project: src\runtime\src\libraries\System.ComponentModel.TypeConverter\src\System.ComponentModel.TypeConverter.csproj (System.ComponentModel.TypeConverter) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace System.ComponentModel { /// <summary> /// Represents a collection of attributes. /// </summary> public class AttributeCollection : ICollection, IEnumerable { internal const string FilterRequiresUnreferencedCodeMessage = "The public parameterless constructor or the 'Default' static field may be trimmed from the Attribute's Type."; /// <summary> /// An empty AttributeCollection that can used instead of creating a new one. /// </summary> public static readonly AttributeCollection Empty = new AttributeCollection(null); private static Dictionary<Type, Attribute?>? s_defaultAttributes; private readonly Attribute[] _attributes; private static readonly object s_internalSyncObject = new object(); private struct AttributeEntry { public Type type; public int index; } private const int FoundTypesLimit = 5; private AttributeEntry[]? _foundAttributeTypes; private int _index; /// <summary> /// Creates a new AttributeCollection. /// </summary> public AttributeCollection(params Attribute[]? attributes) { _attributes = attributes ?? Array.Empty<Attribute>(); for (int idx = 0; idx < _attributes.Length; idx++) { ArgumentNullException.ThrowIfNull(_attributes[idx], nameof(attributes)); } } protected AttributeCollection() : this(Array.Empty<Attribute>()) { } /// <summary> /// Creates a new AttributeCollection from an existing AttributeCollection /// </summary> public static AttributeCollection FromExisting(AttributeCollection existing, params Attribute[]? newAttributes) { ArgumentNullException.ThrowIfNull(existing); newAttributes ??= Array.Empty<Attribute>(); Attribute[] newArray = new Attribute[existing.Count + newAttributes.Length]; int actualCount = existing.Count; existing.CopyTo(newArray, 0); for (int idx = 0; idx < newAttributes.Length; idx++) { ArgumentNullException.ThrowIfNull(newAttributes[idx], nameof(newAttributes)); // We must see if this attribute is already in the existing // array. If it is, we replace it. bool match = false; for (int existingIdx = 0; existingIdx < existing.Count; existingIdx++) { if (newArray[existingIdx].TypeId.Equals(newAttributes[idx].TypeId)) { match = true; newArray[existingIdx] = newAttributes[idx]; break; } } if (!match) { newArray[actualCount++] = newAttributes[idx]; } } // Now, if we collapsed some attributes, create a new array. Attribute[] attributes; if (actualCount < newArray.Length) { attributes = new Attribute[actualCount]; Array.Copy(newArray, attributes, actualCount); } else { attributes = newArray; } return new AttributeCollection(attributes); } /// <summary> /// Gets the attributes collection. /// </summary> protected internal virtual Attribute[] Attributes => _attributes; /// <summary> /// Gets the number of attributes. /// </summary> public int Count => Attributes.Length; /// <summary> /// Gets the attribute with the specified index number. /// </summary> public virtual Attribute this[int index] => Attributes[index]; /// <summary> /// Gets the attribute with the specified type. /// </summary> public virtual Attribute? this[[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] Type attributeType] { get { ArgumentNullException.ThrowIfNull(attributeType); lock (s_internalSyncObject) { // 2 passes here for perf. Really! first pass, we just // check equality, and if we don't find it, then we // do the IsAssignableFrom dance. Turns out that's // a relatively expensive call and we try to avoid it // since we rarely encounter derived attribute types // and this list is usually short. _foundAttributeTypes ??= new AttributeEntry[FoundTypesLimit]; int ind = 0; for (; ind < FoundTypesLimit; ind++) { if (_foundAttributeTypes[ind].type == attributeType) { int index = _foundAttributeTypes[ind].index; if (index != -1) { return Attributes[index]; } else { return GetDefaultAttribute(attributeType); } } if (_foundAttributeTypes[ind].type == null) break; } ind = _index++; if (_index >= FoundTypesLimit) { _index = 0; } _foundAttributeTypes[ind].type = attributeType; int count = Attributes.Length; for (int i = 0; i < count; i++) { Attribute attribute = Attributes[i]; Type aType = attribute.GetType(); if (aType == attributeType) { _foundAttributeTypes[ind].index = i; return attribute; } } // Now check the hierarchies. for (int i = 0; i < count; i++) { Attribute attribute = Attributes[i]; if (attributeType.IsInstanceOfType(attribute)) { _foundAttributeTypes[ind].index = i; return attribute; } } _foundAttributeTypes[ind].index = -1; return GetDefaultAttribute(attributeType); } } } /// <summary> /// Determines if this collection of attributes has the specified attribute. /// </summary> [RequiresUnreferencedCode(FilterRequiresUnreferencedCodeMessage)] public bool Contains(Attribute? attribute) { if (attribute == null) { return false; } Attribute? attr = this[attribute.GetType()]; return attr != null && attr.Equals(attribute); } /// <summary> /// Determines if this attribute collection contains the all /// the specified attributes in the attribute array. /// </summary> [RequiresUnreferencedCode(FilterRequiresUnreferencedCodeMessage)] public bool Contains(Attribute[]? attributes) { if (attributes == null) { return true; } for (int i = 0; i < attributes.Length; i++) { if (!Contains(attributes[i])) { return false; } } return true; } /// <summary> /// Returns the default value for an attribute. This uses the following heuristic: /// 1. It looks for a public static field named "Default". /// </summary> protected Attribute? GetDefaultAttribute([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] Type attributeType) { ArgumentNullException.ThrowIfNull(attributeType); lock (s_internalSyncObject) { s_defaultAttributes ??= new Dictionary<Type, Attribute?>(); // If we have already encountered this, use what's in the table. if (s_defaultAttributes.TryGetValue(attributeType, out Attribute? defaultAttribute)) { return defaultAttribute; } Attribute? attr = null; // Not in the table, so do the legwork to discover the default value. Type reflect = TypeDescriptor.GetReflectionType(attributeType); FieldInfo? field = reflect.GetField("Default", BindingFlags.Public | BindingFlags.Static | BindingFlags.GetField); if (field != null && field.IsStatic) { attr = (Attribute?)field.GetValue(null); } else { ConstructorInfo? ci = reflect.UnderlyingSystemType.GetConstructor(Type.EmptyTypes); if (ci != null) { attr = (Attribute)ci.Invoke(Array.Empty<object>()); // If we successfully created, verify that it is the // default. Attributes don't have to abide by this rule. if (!attr.IsDefaultAttribute()) { attr = null; } } } s_defaultAttributes[attributeType] = attr; return attr; } } /// <summary> /// Gets an enumerator for this collection. /// </summary> public IEnumerator GetEnumerator() => Attributes.GetEnumerator(); /// <summary> /// Determines if a specified attribute is the same as an attribute /// in the collection. /// </summary> public bool Matches(Attribute? attribute) { for (int i = 0; i < Attributes.Length; i++) { if (Attributes[i].Match(attribute)) { return true; } } return false; } /// <summary> /// Determines if the attributes in the specified array are /// the same as the attributes in the collection. /// </summary> public bool Matches(Attribute[]? attributes) { if (attributes == null) { return true; } for (int i = 0; i < attributes.Length; i++) { if (!Matches(attributes[i])) { return false; } } return true; } bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => this; int ICollection.Count => Count; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// <summary> /// Copies this collection to an array. /// </summary> public void CopyTo(Array array, int index) => Array.Copy(Attributes, 0, array, index, Attributes.Length); } }