|
// 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.
// 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,
}
}
|