Today, we will continue to enjoy some common extension methods related to enumeration operations.
Today we are going to share the implementation of converting enumeration values to enumerations, enumeration names and enumeration descriptions.
Let's start by modifying the normal enumeration that was defined for testing in the previous article by adding a new enumeration item with the following code:
//Normal enumeration
internal enum StatusEnum
{
[Description("Normal")]
Normal = 0,
[Description("Standby")]
Standby = 1, [Description("Normal")] Normal = 0, [Description("Standby")]
[Description("Offline")]
Offline = 2,
Online = 3, [Description("Offline")]
Fault = 4, [Description("Offline")] Offline = 2, Online = 3,
}
01, converted to enumeration based on enumeration values
This method takes an enumeration value as an argument and converts it to the corresponding enumeration, or returns null if the conversion fails.
Enum class Enum comes with two conversion methods, one of which was used in the previous article that Parse, this method can receive string or Type type as a parameter, and the second is the ToObject method, receiving parameters for the integer type.
Since the enumeration value itself is of integer type, we choose the ToObject method as the final implementation, which avoids the need to convert the integer type parameter when using the Parse method.
Meanwhile, we can find eight possible types of enumeration values through the above figure: uint, ulong, ushort, sbyte, long, int, byte, short.
Therefore, the following we int type as an example, to illustrate, but at the same time, taking into account the back of the generality, extensibility, we then encapsulate a public implementation of the generic can be done to support the above eight types. Therefore, this method will call an internal private method, as follows:
//Convert to an enumeration based on the value of the enumeration, conversion fails to return null
public static T? ToEnumByValue<T>(this int value) where T : struct, Enum
{
//Call the enumeration value according to the enumeration into the enumeration method
return ToEnumByValue<int, T>(value);
}
The internal private method that is achieved through generic support for a variety of types, we first look at the code implementation and then explain in detail, the specific code is as follows:
// Convert to enumeration based on the value of the enumeration, if the conversion fails, return null.
private static TEnum? ToEnumByValue<TSource, TEnum>(this TSource value)
where TSource : struct
where TEnum : struct, Enum
{
// Check if the integer value is a valid enumeration value and if it is a valid bit-flagged enumeration combination item.
if (! (typeof(TEnum), value) && !IsValidFlagsMask<TSource, TEnum>(value))
{
// return null if data is not valid
return default;
}
//Valid enum values are converted
return (TEnum)(typeof(TEnum), value); }
}
This method first validates the legitimacy of the parameter, and the validation passes the direct conversion using the ToObject method.
Parameter validation begins with a method check to see if the parameter is a valid enumeration item. This is because both the ToObject method and the Parse method are successful at converting an integer type parameter, regardless of whether or not the parameter is an item in the enumeration, so we need to first check out items that are not in the enumeration.
The method IsValidFlagsMask method is mainly for the combination of bit flag enumeration, bit flag enumeration characteristics lead to even if we do not define the relevant items in the enumeration, but can be obtained through the combination and is legal, so we need to deal with the bit flag enumeration separately, the specific implementation of the code is as follows:
// Store whether the enumeration is a bit-flag enumeration.
private static readonly ConcurrentDictionary<Type, bool> _flags = new();
// Stores the mask value corresponding to the enumeration.
private static readonly ConcurrentDictionary<Type, long> _flagsMasks = new();
private static bool IsValidFlagsMask<TSource, TEnum>(TSource source)
where TSource : struct
where TEnum : struct, Enum
Enum
var type = typeof(TEnum);
// Determine whether the enumeration is a bit flags, if there is a cache directly get, otherwise calculate and store in the cache and then return
var isFlags = _flags.GetOrAdd(type, (key) =>.
{
// Check if the enumeration type has the Flags feature.
return (key, typeof(FlagsAttribute));
});
// Return false if it is not a bit-flag enumeration
if (!isFlags)
{
return false; }); // return false if not a bit flag enumeration if (!isFlags) {
}
//Get the enumeration mask, if it's cached get it directly, otherwise compute it and put it in the cache and return it
var mask = _flagsMasks.GetOrAdd(type, (key) =>
{
// Initialize the storage mask variable
var mask = 0L;
// Get all the values of the enumeration
var values = (key);
// Iterate over all the values of the enumeration and merge all the enumeration values by bitwise or arithmetic operations
foreach (Enum enumValue in values)
{
//Convert the enum value to a long type
var valueLong = Convert.ToInt64(enumValue); // Filter out negative or invalid values.
// Filter out negative or invalid values, canonical bit-flagged enumerations should always be non-negative
if (valueLong >= 0)
{
// Merge the enumeration values into mask
mask |= valueLong; }
}
}
// Return the enum mask with all the enum values.
return mask; }
}); var value = Convert.
var value = Convert.ToInt64(source); //Inverse the value to be verified with the enumeration mask to do the sum operation.
//Inverse the value to be verified and the enumeration mask to do an and operation.
// The result is equal to 0 which means value is a valid enumeration value.
return (value & ~mask) == 0; }
}
This method first determines whether the current enumeration is a bit-flags enumeration, i.e., whether the enumeration has the Flags feature, which can be implemented, taking into account the performance issue, so we put the enumeration into the cache whether the enumeration is a bit-flags enumeration, and then do not have to determine it again when it is used for the next time.
Returns false if the current enumeration is not a bit-flag enumeration.
If it's a bit-flag enumeration, then we're getting to the key point, how do you determine if a value is one of a set of values or any combination of a set of values?
The bit masking technique is used here for the purpose of combining all enumeration items by tagging them by bit or, along with possible combinations.
Here the variable storing the mask is defined as a long type because we need to be compatible with the eight integer types mentioned above. Also a compliant bit flag enumeration design enumeration value will not have a negative number so it needs to be filtered out.
Considering the performance issue, it is also necessary to record the enumeration mask of each enumeration pair into the cache for next time use.
After getting the enumeration mask we need to invert it, that is to say, all the values that meet the requirements, this value and then to be verified with the parameter to be done by the bit with the operation, if it is not 0 that is to be verified to be invalid enumeration value, otherwise it is a valid enumeration value.
About the bit operation we will find an opportunity later to explain the principles and mysteries of the separate detailed explanation.
Explain the entire implementation process we also need to carry out detailed unit testing of the method, specifically divided into the following cases:
(1) Normal enumeration value, successfully converted to enumeration;
(2) An enumeration value that does not exist, but can be obtained by enumeration item by bit or merge, returns null;
(3) An enumeration value that does not exist and cannot be obtained by bitwise or merged enumeration entries returns null;
(4) Normal bit flag enumeration value, successfully converted to enumeration;
(5) Enumeration values that do not exist, but can be successfully converted to enumerations by enumeration items obtained by bitwise or merging;
(6) An enumeration value that does not exist and cannot be obtained by bitwise or merged enumeration entries returns null;
The specific implementation code is as follows:
[Fact]
public void ToEnumByValue()
{
// Normal enumeration value, successfully converted to enumeration
var status = <StatusEnum>().
(, status);
//Nonexistent enumeration value, but can be obtained by enumeration item by bit or merge, return null
var isStatusNull = <StatusEnum>();;
(isStatusNull);
// Enumeration value that does not exist, and cannot be obtained from enumeration items by bitwise or merge, return null.
var isStatusNull1 = <StatusEnum>();
(isStatusNull1);
// Normal bit flags enum value, successfully converted to enumeration
var flags = <TypeFlagsEnum>();;
(, flags);
// Non-existing enumeration value, but can be obtained by enumeration item by bit or merge, successfully converted to enumeration
var flagsGroup = <TypeFlagsEnum>();
( | , flagsGroup).
// Enumeration value that does not exist and cannot be bitwise or merged by enumeration item, returns null.
var isFlagsNull = <TypeFlagsEnum>();
(isFlagsNull);
}
02, converted to enumeration or default value based on enumeration value
This method is complementary to the previous method and is used to handle the fact that if the conversion is unsuccessful, then a specified default enumeration is returned, as described in the following code:
//Convert to an enumeration based on the value of the enumeration, the conversion fails to return to the default enumeration
public static T? ToEnumOrDefaultByValue<T>(this int value, T defaultValue)
where T : struct, Enum
{
// Call the convert to enum method based on the enum value
var result = <T>();
if ()
{
// Return the enumeration
return ;
}
//Failure to convert returns the default enumeration.
return defaultValue; }
}
Then we run a simple unit test with the following code:
[Fact]
public void ToEnumOrDefaultByValue()
{
// Normal enumeration value, successfully converted to enumeration.
var status = ();
(, status);
// Non-existent enumeration value, return the specified default enumeration
var statusDefault = (); (, statusDefault); //Non-existent enumeration value, return the specified default enumeration.
(, statusDefault);
}
03The enumeration value is converted to an enumeration name.
This method takes an enumeration value as an argument and converts it to the corresponding enumeration name, or returns null if the conversion fails.
The implementation is to get the enumeration by converting it to an enumeration method based on the enumeration value, and then get the enumeration name through the enumeration, as shown in the following code:
//Convert the value of the enumeration to the name of the enumeration, if the conversion fails, return null.
public static string? ToEnumNameByValue<T>(this int value) where T : struct, Enum
EnumNameByValue
// Call the EnumNameByValue method.
var result = <T>();
if ()
{
//return the name of the enumeration
return ();
}
// Returns null if the conversion fails
return default; }
}
We perform the following unit test:
[Fact]
public void ToEnumNameByValue()
{
// Normal enumeration value, successfully converted to enumeration name.
var status = <StatusEnum>();
("Standby", status);
//Null for non-existing enum values.
var isStatusNull = <StatusEnum>();
(isStatusNull);
// Normal bit flags enum value, successfully converted to enum name
var flags = <TypeFlagsEnum>();;
("HttpAndUdp", flags);
// Returns null for non-existent bit flag enumeration values.
var isFlagsNull = <TypeFlagsEnum>();
(isFlagsNull);
}
04The default value of the enumeration name is converted to the default value of the enumeration name based on the value of the enumeration.
This method is complementary to the previous method and is used to handle the fact that if the conversion is unsuccessful, then a specified default enumeration name is returned, as described in the following code:
//Convert to enum name based on the value of the enumeration, if the conversion fails, return the default enum name
public static string? ToEnumNameOrDefaultByValue<T>(this int value, string defaultValue)
where T : struct, Enum
{
// Call the EnumNameOrDefaultByValue method.
var result = <T>();
if (! (result))
{
// Return the name of the enumeration
return result; }
}
// If the conversion fails, return the default enumeration name.
return defaultValue; }
}
Perform a simple unit test, the specific code is as follows:
[Fact]
public void ToEnumNameOrDefaultByValue()
{
// Normal enum value, successfully converted to enum name.
var status = <StatusEnum>("Offline");
("Standby", status);
// Returns the default enumeration name if it doesn't exist.
var statusDefault = <StatusEnum>("Offline");
("Offline", statusDefault);
}
05The enumeration value is converted to an enumeration description.
This method takes an enumeration value as an argument and converts it to the corresponding enumeration name, or returns null if the conversion fails.
The implementation is to get an enumeration by converting it to an enumeration method based on the enumeration value, and then get the enumeration description through the enumeration, as shown in the following code:
//Convert the value of the enumeration to the description of the enumeration, if the conversion fails, return null.
public static string? ToEnumDescByValue<T>(this int value) where T : struct, Enum
EnumDescByValue.
// Call the enumeration method based on the value of the enumeration
var result = <T>();
if ()
{
// Return the enumeration description
return ();
}
// Returns null if the conversion fails
return default; }
}
The unit tests are as follows:
[Fact]
public void ToEnumDescByValue()
{
// Normal bit flags enum value, successfully converted to enum description
var flags = <TypeFlagsEnum>();
("Http protocol, Udp protocol", flags);
// Normal bit flags enum value, combination of items does not exist, successfully converted to enum description
var flagsGroup1 = <TypeFlagsEnum>();
("Http Protocol,Tcp Protocol", flagsGroup1);;
}
06The enumeration value is converted to an enumeration description default value based on the enumeration value.
This method is complementary to the previous method and is used to handle the fact that if the conversion is unsuccessful, then a description of the specified default enumeration is returned, as described in the following code:
//Convert to enum description based on enum value, if conversion fails, return to default enum description.
public static string? ToEnumDescOrDefaultByValue<T>(this int value, string defaultValue)
where T : struct, Enum
{
// Call the EnumDescOrDefaultByValue method.
var result = <T>();
if (! (result))
{
// Return the enumeration description
return result; }
}
// If the conversion fails, return the default enumeration description.
return defaultValue; }
}
The unit test code is as follows:
[Fact]
public void ToEnumDescOrDefaultByValue()
{
// Normal enum value, successfully converted to enum description.
var status = <StatusEnum>("Testing");
("Standby", status);
// For non-existing enum values, return the specified default enum description
var statusDefault = <StatusEnum>("Testing");
("Test", statusDefault);
}
Later on I will upload the library to Nuget so you can use it directly.
classifier for sums of money: The test method code as well as the sample source code have been uploaded to the code repository for those who are interested./hugogoos/Ideal