diff --git a/src/mscorlib/src/System/Array.cs b/src/mscorlib/src/System/Array.cs index 9ace3f2f60e6..f39e33234af1 100644 --- a/src/mscorlib/src/System/Array.cs +++ b/src/mscorlib/src/System/Array.cs @@ -39,8 +39,10 @@ public static ReadOnlyCollection AsReadOnly(T[] array) { } Contract.Ensures(Contract.Result>() != null); + // If the array is empty, return the cached, empty ReadOnlyCollection + // instance to avoid allocating an unnecessary object. // T[] implements IList. - return new ReadOnlyCollection(array); + return array.Length == 0 ? EmptyReadOnlyCollection.Value : new ReadOnlyCollection(array); } [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] diff --git a/src/mscorlib/src/System/Collections/ObjectModel/ReadOnlyCollection.cs b/src/mscorlib/src/System/Collections/ObjectModel/ReadOnlyCollection.cs index a9b2bead534b..9bab7f1475c5 100644 --- a/src/mscorlib/src/System/Collections/ObjectModel/ReadOnlyCollection.cs +++ b/src/mscorlib/src/System/Collections/ObjectModel/ReadOnlyCollection.cs @@ -228,4 +228,11 @@ void IList.RemoveAt(int index) { ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection); } } + + // Useful in number of places that return an empty ReadOnlyCollection to avoid unnecessary memory allocation. + internal static class EmptyReadOnlyCollection + { + public static readonly ReadOnlyCollection Value = + new ReadOnlyCollection(Array.Empty()); + } } diff --git a/src/mscorlib/src/System/Reflection/CustomAttribute.cs b/src/mscorlib/src/System/Reflection/CustomAttribute.cs index 2e54c886c294..71fbff977adf 100644 --- a/src/mscorlib/src/System/Reflection/CustomAttribute.cs +++ b/src/mscorlib/src/System/Reflection/CustomAttribute.cs @@ -304,34 +304,35 @@ private static CustomAttributeType InitCustomAttributeType(RuntimeType parameter [System.Security.SecurityCritical] // auto-generated private static IList GetCustomAttributes(RuntimeModule module, int tkTarget) { - CustomAttributeRecord[] records = GetCustomAttributeRecords(module, tkTarget); + CustomAttributeRecordCollection records = GetCustomAttributeRecordCollection(module, tkTarget); - CustomAttributeData[] customAttributes = new CustomAttributeData[records.Length]; - for (int i = 0; i < records.Length; i++) - customAttributes[i] = new CustomAttributeData(module, records[i]); - - return Array.AsReadOnly(customAttributes); - } - #endregion - - #region Internal Static Members - [System.Security.SecurityCritical] // auto-generated - internal unsafe static CustomAttributeRecord[] GetCustomAttributeRecords(RuntimeModule module, int targetToken) - { - MetadataImport scope = module.MetadataImport; + CustomAttributeData[] customAttributes = null; + int recordIndex = 0; + foreach (CustomAttributeRecord record in records) + { + // Create the customAttributes array only if it's going to be non-empty. + if (customAttributes == null) + { + customAttributes = new CustomAttributeData[records.Count]; + } - MetadataEnumResult tkCustomAttributeTokens; - scope.EnumCustomAttributes(targetToken, out tkCustomAttributeTokens); + customAttributes[recordIndex++] = new CustomAttributeData(module, record); + } - CustomAttributeRecord[] records = new CustomAttributeRecord[tkCustomAttributeTokens.Length]; + // If there weren't any custom attributes, return the empty array instance + // instead of allocating a new empty array. + // Array.AsReadOnly() returns a cached instance for an empty input array + // so won't cause an allocation in that case. + return Array.AsReadOnly(customAttributes ?? Array.Empty()); + } + #endregion - for (int i = 0; i < records.Length; i++) - { - scope.GetCustomAttributeProps( - tkCustomAttributeTokens[i], out records[i].tkCtor.Value, out records[i].blob); - } + #region Internal Static Members - return records; + [System.Security.SecurityCritical] + internal unsafe static CustomAttributeRecordCollection GetCustomAttributeRecordCollection(RuntimeModule module, int targetToken) + { + return new CustomAttributeRecordCollection(module.MetadataImport, targetToken); } internal static CustomAttributeTypedArgument Filter(IList attrs, Type caType, int parameter) @@ -579,6 +580,127 @@ public virtual IList NamedArguments } } #endregion + + #region Record Enumeration / Enumerator + + internal struct CustomAttributeRecordCollection : IReadOnlyList + { + private readonly MetadataImport _scope; + private readonly MetadataEnumResult _tkCustomAttributeTokens; + + internal CustomAttributeRecordCollection(MetadataImport scope, int targetToken) + { + _scope = scope; + _scope.EnumCustomAttributes(targetToken, out _tkCustomAttributeTokens); + } + + public CustomAttributeRecord this[int index] + { + get + { + if (index < 0 || index >= this.Count) + { + throw new IndexOutOfRangeException(); + } + Contract.EndContractBlock(); + + CustomAttributeRecord currentRecord; + _scope.GetCustomAttributeProps( + _tkCustomAttributeTokens[index], out currentRecord.tkCtor.Value, out currentRecord.blob); + return currentRecord; + } + } + + public int Count + { + get + { + Contract.Ensures(Contract.Result() >= 0); + Contract.EndContractBlock(); + + return _tkCustomAttributeTokens.Length; + } + } + + public CustomAttributeRecordEnumerator GetEnumerator() + { + return new CustomAttributeRecordEnumerator(_scope, _tkCustomAttributeTokens); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + internal struct CustomAttributeRecordEnumerator : IEnumerator + { + private readonly MetadataImport _scope; + private readonly MetadataEnumResult _tkCustomAttributeTokens; + private int _nextIndex; // I.e., current index + 1 + + internal CustomAttributeRecordEnumerator(MetadataImport scope, MetadataEnumResult customAttributeTokens) + { + _scope = scope; + _tkCustomAttributeTokens = customAttributeTokens; + _nextIndex = 0; + } + + public CustomAttributeRecord Current + { + get + { + if (_nextIndex <= 0) + { + throw new InvalidOperationException(); + } + Contract.EndContractBlock(); + + CustomAttributeRecord currentRecord; + _scope.GetCustomAttributeProps( + _tkCustomAttributeTokens[_nextIndex - 1], out currentRecord.tkCtor.Value, out currentRecord.blob); + return currentRecord; + } + } + + object IEnumerator.Current + { + get + { + return this.Current; + } + } + + public void Dispose() + { + // Nothing to dispose. + } + + public bool MoveNext() + { + if (_nextIndex >= _tkCustomAttributeTokens.Length) + { + return false; + } + else + { + _nextIndex += 1; + return true; + } + } + + public void Reset() + { + _nextIndex = 0; + } + } + + #endregion } [Serializable] @@ -1594,7 +1716,8 @@ private static bool IsCustomAttributeDefined( throw new InvalidOperationException(Environment.GetResourceString("Arg_ReflectionOnlyCA")); Contract.EndContractBlock(); - CustomAttributeRecord[] car = CustomAttributeData.GetCustomAttributeRecords(decoratedModule, decoratedMetadataToken); + CustomAttributeData.CustomAttributeRecordCollection cars = + CustomAttributeData.GetCustomAttributeRecordCollection(decoratedModule, decoratedMetadataToken); if (attributeFilterType != null) { @@ -1609,10 +1732,8 @@ private static bool IsCustomAttributeDefined( // we can cache the successful APTCA check between the decorated and the declared assembly. Assembly lastAptcaOkAssembly = null; - for (int i = 0; i < car.Length; i++) + foreach (CustomAttributeRecord caRecord in cars) { - CustomAttributeRecord caRecord = car[i]; - if (FilterCustomAttributeRecord(caRecord, scope, ref lastAptcaOkAssembly, decoratedModule, decoratedMetadataToken, attributeFilterType, mustBeInheritable, null, null, out attributeType, out ctor, out ctorHasParameters, out isVarArg)) @@ -1624,10 +1745,8 @@ private static bool IsCustomAttributeDefined( Contract.Assert(attributeFilterType == null); Contract.Assert(!MetadataToken.IsNullToken(attributeCtorToken)); - for (int i = 0; i < car.Length; i++) + foreach (CustomAttributeRecord caRecord in cars) { - CustomAttributeRecord caRecord = car[i]; - if (caRecord.tkCtor == attributeCtorToken) return true; } @@ -1653,15 +1772,16 @@ private unsafe static object[] GetCustomAttributes( Contract.EndContractBlock(); MetadataImport scope = decoratedModule.MetadataImport; - CustomAttributeRecord[] car = CustomAttributeData.GetCustomAttributeRecords(decoratedModule, decoratedMetadataToken); + CustomAttributeData.CustomAttributeRecordCollection cars = + CustomAttributeData.GetCustomAttributeRecordCollection(decoratedModule, decoratedMetadataToken); bool useObjectArray = (attributeFilterType == null || attributeFilterType.IsValueType || attributeFilterType.ContainsGenericParameters); Type arrayType = useObjectArray ? typeof(object) : attributeFilterType; - if (attributeFilterType == null && car.Length == 0) + if (attributeFilterType == null && cars.Count == 0) return CreateAttributeArrayHelper(arrayType, 0); - object[] attributes = CreateAttributeArrayHelper(arrayType, car.Length); + object[] attributes = CreateAttributeArrayHelper(arrayType, cars.Count); int cAttributes = 0; // Custom attribute security checks are done with respect to the assembly *decorated* with the @@ -1678,10 +1798,9 @@ private unsafe static object[] GetCustomAttributes( // we can cache the successful APTCA check between the decorated and the declared assembly. Assembly lastAptcaOkAssembly = null; - for (int i = 0; i < car.Length; i++) + foreach (CustomAttributeRecord caRecord in cars) { object attribute = null; - CustomAttributeRecord caRecord = car[i]; IRuntimeMethodInfo ctor = null; RuntimeType attributeType = null; @@ -1827,7 +1946,7 @@ private unsafe static object[] GetCustomAttributes( // finally or CERs here. frame.Pop(); - if (cAttributes == car.Length && pcaCount == 0) + if (cAttributes == cars.Count && pcaCount == 0) return attributes; object[] result = CreateAttributeArrayHelper(arrayType, cAttributes + pcaCount); @@ -2005,13 +2124,13 @@ internal static AttributeUsageAttribute GetAttributeUsage(RuntimeType decoratedA { RuntimeModule decoratedModule = decoratedAttribute.GetRuntimeModule(); MetadataImport scope = decoratedModule.MetadataImport; - CustomAttributeRecord[] car = CustomAttributeData.GetCustomAttributeRecords(decoratedModule, decoratedAttribute.MetadataToken); + CustomAttributeData.CustomAttributeRecordCollection cars = + CustomAttributeData.GetCustomAttributeRecordCollection(decoratedModule, decoratedAttribute.MetadataToken); AttributeUsageAttribute attributeUsageAttribute = null; - for (int i = 0; i < car.Length; i++) + foreach (CustomAttributeRecord caRecord in cars) { - CustomAttributeRecord caRecord = car[i]; RuntimeType attributeType = decoratedModule.ResolveType(scope.GetParentToken(caRecord.tkCtor), null, null) as RuntimeType; if (attributeType != (RuntimeType)typeof(AttributeUsageAttribute))