|
// 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
{
//
// This file collects a set of Enum-related apis that run when the Type is subclassed by an application.
// None of it runs on normal Type objects supplied by the runtime (as those types override these methods.)
//
// Since app-subclassed Types are "untrusted classes" that may or may not implement the complete surface area correctly,
// this code should be considered brittle and not changed lightly.
//
public abstract partial class Type
{
public virtual bool IsEnumDefined(object value)
{
ArgumentNullException.ThrowIfNull(value);
if (!IsEnum)
throw new ArgumentException(SR.Arg_MustBeEnum, nameof(value));
// Check if both of them are of the same type
Type valueType = value.GetType();
// If the value is an Enum then we need to extract the underlying value from it
if (valueType.IsEnum)
{
if (!valueType.IsEquivalentTo(this))
throw new ArgumentException(SR.Format(SR.Arg_EnumAndObjectMustBeSameType, valueType, this));
valueType = valueType.GetEnumUnderlyingType();
}
// If a string is passed in
if (valueType == typeof(string))
{
string[] names = GetEnumNames();
if (Array.IndexOf(names, value) >= 0)
return true;
else
return false;
}
// If an enum or integer value is passed in
if (IsIntegerType(valueType))
{
Type underlyingType = GetEnumUnderlyingType();
// We cannot compare the types directly because valueType is always a runtime type but underlyingType might not be.
if (underlyingType.GetTypeCodeImpl() != valueType.GetTypeCodeImpl())
throw new ArgumentException(SR.Format(SR.Arg_EnumUnderlyingTypeAndObjectMustBeSameType, valueType, underlyingType));
Array values = GetEnumRawConstantValues();
return BinarySearch(values, value) >= 0;
}
else
{
throw new InvalidOperationException(SR.InvalidOperation_UnknownEnumType);
}
}
public virtual string? GetEnumName(object value)
{
ArgumentNullException.ThrowIfNull(value);
if (!IsEnum)
throw new ArgumentException(SR.Arg_MustBeEnum, nameof(value));
Type valueType = value.GetType();
if (!(valueType.IsEnum || IsIntegerType(valueType)))
throw new ArgumentException(SR.Arg_MustBeEnumBaseTypeOrEnum, nameof(value));
Array values = GetEnumRawConstantValues();
int index = BinarySearch(values, value);
if (index >= 0)
{
string[] names = GetEnumNames();
return names[index];
}
return null;
}
public virtual string[] GetEnumNames()
{
if (!IsEnum)
throw new ArgumentException(SR.Arg_MustBeEnum, "enumType");
GetEnumData(out string[] names, out _);
return names;
}
// Returns the enum values as an object array.
private Array GetEnumRawConstantValues()
{
GetEnumData(out _, out Array values);
return values;
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2085:UnrecognizedReflectionPattern",
Justification = "Literal fields on enums can never be trimmed")]
// This will return enumValues and enumNames sorted by the values.
private void GetEnumData(out string[] enumNames, out Array enumValues)
{
FieldInfo[] flds = GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
object[] values = new object[flds.Length];
string[] names = new string[flds.Length];
for (int i = 0; i < flds.Length; i++)
{
names[i] = flds[i].Name;
values[i] = flds[i].GetRawConstantValue()!;
}
// Insertion Sort these values in ascending order.
// We use this O(n^2) algorithm, but it turns out that most of the time the elements are already in sorted order and
// the common case performance will be faster than quick sorting this.
Comparer comparer = Comparer.Default;
for (int i = 1; i < values.Length; i++)
{
int j = i;
string tempStr = names[i];
object val = values[i];
bool exchanged = false;
// Since the elements are sorted we only need to do one comparison, we keep the check for j inside the loop.
while (comparer.Compare(values[j - 1], val) > 0)
{
names[j] = names[j - 1];
values[j] = values[j - 1];
j--;
exchanged = true;
if (j == 0)
break;
}
if (exchanged)
{
names[j] = tempStr;
values[j] = val;
}
}
enumNames = names;
enumValues = values;
}
// Convert everything to ulong then perform a binary search.
private static int BinarySearch(Array array, object value)
{
ulong[] ulArray = new ulong[array.Length];
for (int i = 0; i < array.Length; ++i)
ulArray[i] = Enum.ToUInt64(array.GetValue(i)!);
ulong ulValue = Enum.ToUInt64(value);
return Array.BinarySearch(ulArray, ulValue);
}
internal static bool IsIntegerType(Type t)
{
return t == typeof(int) ||
t == typeof(short) ||
t == typeof(ushort) ||
t == typeof(byte) ||
t == typeof(sbyte) ||
t == typeof(uint) ||
t == typeof(long) ||
t == typeof(ulong) ||
t == typeof(char);
}
}
}
|