// 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. #nullable enable // This file defines an internal class used to throw exceptions in BCL code. // The main purpose is to reduce code size. // // The old way to throw an exception generates quite a lot IL code and assembly code. // Following is an example: // C# source // throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key); // IL code: // IL_0003: ldstr "key" // IL_0008: ldstr "ArgumentNull_Key" // IL_000d: call string System.Environment::GetResourceString(string) // IL_0012: newobj instance void System.ArgumentNullException::.ctor(string,string) // IL_0017: throw // which is 21bytes in IL. // // So we want to get rid of the ldstr and call to Environment.GetResource in IL. // In order to do that, I created two enums: ExceptionResource, ExceptionArgument to represent the // argument name and resource name in a small integer. The source code will be changed to // ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key, ExceptionResource.ArgumentNull_Key); // // The IL code will be 7 bytes. // IL_0008: ldc.i4.4 // IL_0009: ldc.i4.4 // IL_000a: call void System.ThrowHelper::ThrowArgumentNullException(valuetype System.ExceptionArgument) // IL_000f: ldarg.0 // // This will also reduce the Jitted code size a lot. // // It is very important we do this for generic classes because we can easily generate the same code // multiple times for different instantiation. // using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace Microsoft.CodeAnalysis.Collections.Internal { internal static class ThrowHelper { [DoesNotReturn] internal static void ThrowIndexOutOfRangeException() { throw new IndexOutOfRangeException(); } [DoesNotReturn] internal static void ThrowArgumentOutOfRangeException() { throw new ArgumentOutOfRangeException(); } [DoesNotReturn] internal static void ThrowArgumentOutOfRange_IndexMustBeLessException() { throw GetArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_IndexMustBeLess); } [DoesNotReturn] internal static void ThrowArgumentOutOfRange_IndexMustBeLessOrEqualException() { throw GetArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_IndexMustBeLessOrEqual); } [DoesNotReturn] internal static void ThrowArgumentException_BadComparer(object? comparer) { throw new ArgumentException(string.Format(SR.Arg_BogusIComparer, comparer)); } [DoesNotReturn] internal static void ThrowIndexArgumentOutOfRange_NeedNonNegNumException() { throw GetArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); } [DoesNotReturn] internal static void ThrowLengthArgumentOutOfRange_ArgumentOutOfRange_NeedNonNegNum() { throw GetArgumentOutOfRangeException(ExceptionArgument.length, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); } [DoesNotReturn] internal static void ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_IndexMustBeLessOrEqual() { throw GetArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_IndexMustBeLessOrEqual); } [DoesNotReturn] internal static void ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_IndexMustBeLess() { throw GetArgumentOutOfRangeException(ExceptionArgument.startIndex, ExceptionResource.ArgumentOutOfRange_IndexMustBeLess); } [DoesNotReturn] internal static void ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count() { throw GetArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_Count); } [DoesNotReturn] internal static void ThrowWrongKeyTypeArgumentException<T>(T key, Type targetType) { // Generic key to move the boxing to the right hand side of throw throw GetWrongKeyTypeArgumentException(key, targetType); } [DoesNotReturn] internal static void ThrowWrongValueTypeArgumentException<T>(T value, Type targetType) { // Generic key to move the boxing to the right hand side of throw throw GetWrongValueTypeArgumentException(value, targetType); } private static ArgumentException GetAddingDuplicateWithKeyArgumentException(object? key) { return new ArgumentException(string.Format(SR.Argument_AddingDuplicateWithKey, key)); } [DoesNotReturn] internal static void ThrowAddingDuplicateWithKeyArgumentException<T>(T key) { // Generic key to move the boxing to the right hand side of throw throw GetAddingDuplicateWithKeyArgumentException(key); } [DoesNotReturn] internal static void ThrowKeyNotFoundException<T>(T key) { // Generic key to move the boxing to the right hand side of throw throw GetKeyNotFoundException(key); } [DoesNotReturn] internal static void ThrowArgumentException(ExceptionResource resource) { throw GetArgumentException(resource); } private static ArgumentNullException GetArgumentNullException(ExceptionArgument argument) { return new ArgumentNullException(GetArgumentName(argument)); } [DoesNotReturn] internal static void ThrowArgumentNullException(ExceptionArgument argument) { throw GetArgumentNullException(argument); } [DoesNotReturn] internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) { throw new ArgumentOutOfRangeException(GetArgumentName(argument)); } [DoesNotReturn] internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource) { throw GetArgumentOutOfRangeException(argument, resource); } [DoesNotReturn] internal static void ThrowInvalidOperationException(ExceptionResource resource, Exception e) { throw new InvalidOperationException(GetResourceString(resource), e); } [DoesNotReturn] internal static void ThrowNotSupportedException(ExceptionResource resource) { throw new NotSupportedException(GetResourceString(resource)); } [DoesNotReturn] internal static void ThrowArgumentException_Argument_IncompatibleArrayType() { throw new ArgumentException(SR.Argument_IncompatibleArrayType); } [DoesNotReturn] internal static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion() { throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion); } [DoesNotReturn] internal static void ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen() { throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); } [DoesNotReturn] internal static void ThrowInvalidOperationException_ConcurrentOperationsNotSupported() { throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported); } private static ArgumentException GetArgumentException(ExceptionResource resource) { return new ArgumentException(GetResourceString(resource)); } private static ArgumentException GetWrongKeyTypeArgumentException(object? key, Type targetType) { return new ArgumentException(string.Format(SR.Arg_WrongType, key, targetType), nameof(key)); } private static ArgumentException GetWrongValueTypeArgumentException(object? value, Type targetType) { return new ArgumentException(string.Format(SR.Arg_WrongType, value, targetType), nameof(value)); } private static KeyNotFoundException GetKeyNotFoundException(object? key) { return new KeyNotFoundException(string.Format(SR.Arg_KeyNotFoundWithKey, key)); } private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource) { return new ArgumentOutOfRangeException(GetArgumentName(argument), GetResourceString(resource)); } // Allow nulls for reference types and Nullable<U>, but not for value types. // Aggressively inline so the jit evaluates the if in place and either drops the call altogether // Or just leaves null test and call to the Non-returning ThrowHelper.ThrowArgumentNullException [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void IfNullAndNullsAreIllegalThenThrow<T>(object? value, ExceptionArgument argName) { // Note that default(T) is not equal to null for value types except when T is Nullable<U>. if (!(default(T) == null) && value == null) ThrowHelper.ThrowArgumentNullException(argName); } private static string GetArgumentName(ExceptionArgument argument) { switch (argument) { case ExceptionArgument.dictionary: return "dictionary"; case ExceptionArgument.array: return "array"; case ExceptionArgument.info: return "info"; case ExceptionArgument.key: return "key"; case ExceptionArgument.value: return "value"; case ExceptionArgument.startIndex: return "startIndex"; case ExceptionArgument.index: return "index"; case ExceptionArgument.capacity: return "capacity"; case ExceptionArgument.collection: return "collection"; case ExceptionArgument.item: return "item"; case ExceptionArgument.converter: return "converter"; case ExceptionArgument.match: return "match"; case ExceptionArgument.count: return "count"; case ExceptionArgument.action: return "action"; case ExceptionArgument.comparison: return "comparison"; case ExceptionArgument.source: return "source"; case ExceptionArgument.length: return "length"; case ExceptionArgument.destinationArray: return "destinationArray"; case ExceptionArgument.other: return "other"; default: Debug.Fail("The enum value is not defined, please check the ExceptionArgument Enum."); return ""; } } private static string GetResourceString(ExceptionResource resource) { switch (resource) { case ExceptionResource.ArgumentOutOfRange_IndexMustBeLessOrEqual: return SR.ArgumentOutOfRange_IndexMustBeLessOrEqual; case ExceptionResource.ArgumentOutOfRange_IndexMustBeLess: return SR.ArgumentOutOfRange_IndexMustBeLess; case ExceptionResource.ArgumentOutOfRange_Count: return SR.ArgumentOutOfRange_Count; case ExceptionResource.Arg_ArrayPlusOffTooSmall: return SR.Arg_ArrayPlusOffTooSmall; case ExceptionResource.Arg_RankMultiDimNotSupported: return SR.Arg_RankMultiDimNotSupported; case ExceptionResource.Arg_NonZeroLowerBound: return SR.Arg_NonZeroLowerBound; case ExceptionResource.ArgumentOutOfRange_ListInsert: return SR.ArgumentOutOfRange_ListInsert; case ExceptionResource.ArgumentOutOfRange_NeedNonNegNum: return SR.ArgumentOutOfRange_NeedNonNegNum; case ExceptionResource.ArgumentOutOfRange_SmallCapacity: return SR.ArgumentOutOfRange_SmallCapacity; case ExceptionResource.Argument_InvalidOffLen: return SR.Argument_InvalidOffLen; case ExceptionResource.ArgumentOutOfRange_BiggerThanCollection: return SR.ArgumentOutOfRange_BiggerThanCollection; case ExceptionResource.NotSupported_KeyCollectionSet: return SR.NotSupported_KeyCollectionSet; case ExceptionResource.NotSupported_ValueCollectionSet: return SR.NotSupported_ValueCollectionSet; case ExceptionResource.InvalidOperation_IComparerFailed: return SR.InvalidOperation_IComparerFailed; default: Debug.Fail("The enum value is not defined, please check the ExceptionResource Enum."); return ""; } } } // // The convention for this enum is using the argument name as the enum name // internal enum ExceptionArgument { dictionary, array, info, key, value, startIndex, index, capacity, collection, item, converter, match, count, action, comparison, source, length, destinationArray, other, } // // The convention for this enum is using the resource name as the enum name // internal enum ExceptionResource { ArgumentOutOfRange_IndexMustBeLessOrEqual, ArgumentOutOfRange_IndexMustBeLess, ArgumentOutOfRange_Count, Arg_ArrayPlusOffTooSmall, Arg_RankMultiDimNotSupported, Arg_NonZeroLowerBound, ArgumentOutOfRange_ListInsert, ArgumentOutOfRange_NeedNonNegNum, ArgumentOutOfRange_SmallCapacity, Argument_InvalidOffLen, ArgumentOutOfRange_BiggerThanCollection, NotSupported_KeyCollectionSet, NotSupported_ValueCollectionSet, InvalidOperation_IComparerFailed, } }