Enum helper in c# not giving expected result - c#

Basically I am not recieving the correct enum type for some reason and I cannot figure out why, my code is below, many thanks in advance for any pointers/ explanation...
EDIT: type-> changed to anothername (thanks guys for the heads up)
Helper:
public static T Convert<T>(this string str)
{
return (T)Enum.Parse(typeof(T), str, true);
}
Enum values:
public enum anothername
{
SmallText = 100,
Number = 15,
TextArea = 0,
Bool = 0,
Choices = 0,
}
My test:
[Test]
public void EnumGetStringFromEnumType()
{
//arrange
var MaxLength = EnumHelper.Convert<anothername>("TextArea").ToString();
//act
//assert
Assert.AreEqual("TextArea", MaxLength);
}
EDIT:
Thanks, removing the int values solved it!
However... what if I actually wanted to have say values for some enum types and not other e.g.
public enum anothername
{
SmallText = 100,
Number = 15,
TextArea,
Bool,
Choices,
}
Test 2:
[Test]
public void EnumGetIntValueOrStringFromEnumType()
{
//arrange
var MaxLength = EnumHelper.ToEnumSafe<anothername>("TextArea");
//act
//assert
Assert.AreEqual(null, (int)MaxLength);
}
I have exactly the same problem when I try and retrieve the int values, I get incorrect results...
result = 16

The enumeration has duplicate members with the same underlying value as TextArea (Bool and Choices). Although the parse should succeed, the value of ToString on the resulting enum instance is not defined, and may not equal "TextArea" as your assertion is expecting.
From the
Enum.ToString documentation:
If multiple enumeration members have
the same underlying value and you
attempt to retrieve the string
representation of an enumeration
member's name based on its underlying
value, your code should not make any
assumptions about which name the
method will return.
EDIT:
In response to your edit, try this assertion:
var MaxLength = EnumHelper.ToEnumSafe<anothername>("TextArea");
Assert.AreEqual(anotherName.TextArea, MaxLength);
or if you prefer comparing the underlying type:
Assert.AreEqual((int)anotherName.TextArea, (int)MaxLength);
You appear to be under the impression that an enum member is not associated with an underlying value if its value is not explicitly specified. This is not the case; all members of an enum are associated with an underlying value. The rules for the 'implicit' associations are given by (from the language specification):
• If the enum member is the first enum
member declared in the enum type, its
associated value is zero.
• Otherwise,
the associated value of the enum
member is obtained by increasing the
associated value of the textually
preceding enum member by one. This
increased value must be within the
range of values that can be
represented by the underlying type,
otherwise a compile-time error occurs.

Related

How to sort my list object based on order of Enum values

I have an enum:
public enum EnumProductConfig
{
Color,
Fold,
Edge,
Hem,
Lift,
Tapes,
Control,
Clips,
Pull,
Val
}
The order of above values in enum is not alphabatical which is totally ok with me.
Now, I have a list with one of the property of type EnumProductConfig. I want to sort my list based on that property (which is of Enum type) in the order of appearance in the Enum. I do not want to sort it alphabetically. The order must stay as mentioned in my Enum. Hence all rows with value Color must come first. Followed by Fold etc.
Please advise.
myData.OrderBy(d => (int)d.ProductConfig).ToList();
Should do it. Import System.Linq
Enums are really just ints anyway, going from zero and increasing by 1. You can also give each value a specific int value if you want to.
Also, you should not prefix your enum type with the word enum as it is against the Microsoft naming guidelines.
In c#, an enum is a group of numeric value constants.
If not specified, the enum's underlying type is int:
Every enumeration type has an underlying type, which can be any integral type except char. The default underlying type of enumeration elements is int
And every enum member gets a value from 0 to n, where n is the number of members -1:
By default, the first enumerator has the value 0, and the value of each successive enumerator is increased by 1.
Since you didn't specify neither the underlying type nor the values, your enum is equivalent to:
public enum EnumProductConfig : int
{
Color = 0,
Fold = 1,
Edge = 2,
Hem = 3,
Lift = 4,
Tapes = 5,
Control = 6,
Clips = 7,
Pull = 8,
Val = 9
}
This means you can simply order by your EnumProductConfig property:
var list = new List<MyClass>()
var sorted = list.OrderBy(c => c.ProductConfig);
You can see a live demo on rextester.

Enum.TryParse strange behaviour

Why does this test pass? TestEnum doesn't contain and option with value "5". So this test should fail, but it doesn't.
private enum TestEnum
{
FirstOption = 2,
SecontOption = 3
}
[Test]
public void EnumTryParseIntValue()
{
TestEnum enumValue;
bool result = Enum.TryParse<TestEnum>(5.ToString(), out enumValue);
Assert.IsTrue(result);
}
Enum.TryParse Method (String, TEnum)
If value is a name that does not correspond to a named constant of
TEnum, the method returns false. If value is the string representation
of an integer that does not represent an underlying value of the TEnum
enumeration, the method returns an enumeration member whose underlying
value is value converted to an integral type. If this behavior is
undesirable, call the IsDefined method to ensure that a particular
string representation of an integer is actually a member of TEnum.
"Returns an enumeration member whose underlying value is value converted to an integral type"
If the value is not present you get back the integer. I don't consider getting back 5 to be an "enumeration member" but that is how it works. If you parse 2 you get FirstOption.
if (Enum.IsDefined(typeof(TestEnum), 5.ToString()))
{
result = Enum.TryParse<TestEnum>(5.ToString(), out enumValue);
Debug.WriteLine(result);
if (result)
{
Debug.WriteLine(enumValue.ToString());
}
}
Use Enum.IsDefined(Type enumType,Object value) - Returns an indication whether a constant with a specified value exists in a specified enumeration.
MSDN: Enum.IsDefined Method

Changing a method from converting just strings to strings and int

Bear with me as this is a difficult concept for me. I have a method that returns the items in an enum as strings (for DB storage). Here is the method:
public static IEnumerable<SelectListItem> GetItemsFromEnum<T>
(T selectedValue = default(T)) where T : struct
{
return from name in Enum.GetNames(typeof(T))
let enumValue = Convert.ToString((T)Enum.
Parse(typeof(T), name, true))
select new SelectListItem
{
Text = GetEnumDescription(name, typeof(T)),
Value = enumValue,
Selected = enumValue.Equals(selectedValue)
};
}
(NB: GetEnumDescription is another method in the class that gets the [Display(Name="") text to display something friendly in the UI.)
For a single select input (radio button, drop down), this works great. However, with multi-selects (list box, check box list), I am thinking of using the enum [Flags] attribute to store in the DB as an int.
However, the GetItemsFromEnum method will not work in the case that I use [Flags] and requires that I change it so that the value assigned in the GetItemsFromEnum method isn't the string value of the enum, it's the int value.
This is actually a two-parter:
How can I modify the method so that enumValue would be the
value of the int from enum [Flags] (i.e., if "1 + 2" were selected
(two check boxes), then the int value saved would be "3")?
Most importantly, what kind of logic can I insert in the method
so that all my other enum's (those without [Flags]) are not
affected (i.e., it still converts ToString)? I was thinking some if ... else logic, but would that work?
First of all, the [Flags] attribute doesn't add special properties to the enum's values, it's just used through Reflection by the Enum class when formatting it as a string and to let people who use your code know that they can use bitwise operations on the enum safely, or in other words, without undesired results.
Let's take this enum:
[Flags]
enum MyEnum
{
Undefined,
Employee,
Student
}
And let's create a variable this way:
var employeeAndStudent = MyEnum.Employee | MyEnum.Student;
The variable, if cast to integer, is equals to 3. While the ToString() method will print "Employee, Student", Enum.GetNames doesn't have a value assigned for the field 3 -- Quite obviously, since it has no name. I can think of two solutions:
You create your own GetNames which will return, in addition to the defined names, also all the possible combinations of the enum's values, conveniently printed in your format.
You define the flags in the enum, which can become pretty wasteful if there are many members. In our case, MyEnum would become:
[Flags]
enum MyEnum
{
Undefined,
Employee,
Student,
EmployeeAndStudent
}
One more thing, your method seems to have a bug. This line:
Selected = enumValue.Equals(selectedValue)
Is trying to compare an enum type to a string, and will always result in false. Are you sure that SelectListItem.Value should be of type string? I suggest you to review your code.
To give direct answers to your question:
Add [Flags] to your enum and use the OR operator to combine values.
In your GetEnumDescription method, check if the type has the Flags attribute.

Building generic method for List<Enum>

In my application, I have something like:
public enum Locations {
LocationA,
LocationB,
LocationC
}
private List<Locations> _myLocations;
public Int64 PackedLocations {
get {
return PackEnumList(this._myLocations);
}
}
So: an enum (backed by int), a List of those enum values, and a read-only property which returns the result of a method I've left out so far.
That method, PackEnumList, is meant to give me a 64-bit integer where each BIT denotes whether the corresponding enum value was selected or not in a list of unique enum values. So in my example above, if _myLocations has only one item: {Locations.LocationA}, the result would be 1 (binary: ...00001), if we then add Locations.LocationC to that list, the result would be 5 (binary: ...000101). The implementation isn't important right now (but I'll include it below for completion/interest/feedback), but the signature of that method is:
public Int64 PackEnumList(List<Enum> listOfEnumValues) {
...
}
When I compile, I get an error that "the best overloaded method ... has some invalid arguments".
I'm guessing this is because _myLocations is being seen as a List of int values, but I'd like PackEnumList() to work even if the enumeration being used were backed by something else, if possible.
Is there a more appropriate way to make a method which will accept a List/Collection of any enumeration?
For completeness, here's the rest of what I'm trying to do (these are static because they're in a shared utility class). These are completely untested yet (since I can't get past the compile error when calling the pack method), so take them with a grain of salt. And there might be a better way to do this, I'm doing this half to solve an interesting problem, and half because I think it is an interesting problem.
public static Int64 PackEnumList(List<Enum> listOfEnumValues) {
BitArray bits = new BitArray(64, defaultValue: false);
foreach (var value in listOfEnumValues) {
// get integer value of each Enum in the List:
int val = Convert.ToInt32(value);
if (val >= 64) {
// this enum has more options than we have bits, so cannot pack
throw new Exception("Enum value out of range for packing: " + val.ToString());
}
bits[val] = true;
}
var res = new Int64[1];
bits.CopyTo(res, 0);
return res[0];
}
// (this method is a little farther from the ideal: the resulting list will need
// to be matched by the caller to the appropriate List of Enums by casting
// each Int32 value to the Enum object in the list)
public static List<Int32> UnpackEnumList(Int64 packedValue) {
string binaryString = Convert.ToString(packedValue, 2);
List<Int32> res = new List<Int32>();
for (int pos = 0; pos < binaryString.Length; pos++) {
if (binaryString[binaryString.Length - pos - 1] == '1') {
// bit is on
res.Add(pos);
}
}
return res;
}
Is there a more appropriate way to make a method which will accept a List/Collection of any enumeration?
Within straight C#? Nope. But you can fudge it...
I have a project called Unconstrained Melody which allows you to make a generic method with a constraint of "T must be an enum type" or "T must be a delegate type". These are valid constraints at the IL level, but can't be expressed in C#
Basically Unconstrained Melody consists of two parts:
A library of useful methods with those constraints, where the source code is written using valid C# which doesn't actually represent those constraints, but uses a marker interface
An IL-rewriting project (ugly but servicable) which converts those constraints into the real "unspeakable" ones
(The expectation is that users of the library would just use the rewritten binary.)
It sounds like you could use the latter part of the project for your code here. It won't be terribly pleasant, but it would work. You might also find the library part useful.
As a side thought, you might want to consider using a [Flags]-style enum instead:
[Flags]
public enum Locations {
LocationA = 1 << 0,
LocationB = 1 << 1,
LocationC = 1 << 2
}
Change your method signature to public Int64 PackEnumList(IEnumerable<Enum> listOfEnumValues)
And then call it like following:
public Int64 PackedLocations
{
get { return PackEnumList(this._myLocations.Cast<Enum>()); }
}
A List<Enum> is not a List<Locations> nor a List<Int32>. Use a generic method to handle the list:
public static void PackEnumList<T>(IEnumerable<T> list) where T : IConvertible
{
foreach (var value in list)
int numeric = value.ToInt32();
// etc.
}
I'd change your signature method to:
public Int64 PackEnumList<T>(IEnumerable<T> listOfEnumValues) where T : struct, IFormattable, IConvertible {
...
}
The where T : struct... constrains it to enum types only (any any other struct implementing both interfaces, which is probably very low)

Ordering of elements in C# enumeration with the same integer value

I have a simple enum:
enum E
{
FullNameForA = 1,
A = 1,
FullNameForB = 2,
B = 2
}
The goal is to be able to use different string values for the same integral values with a twist - FullNameFor* must be used as default. In other words, a user can provide E.A as an input but the code should use E.FullNameForA for output.
It seems like by default C# will use alphabetical ordering of elements with the same integral value, which makes my goal harder. Is that right? Any ideas how to overcome this?
It seems like by default C# will use alphabetical ordering of elements with the same integral value
In what context? When you convert the value back to a string? From the docs of Enum.ToString:
If multiple enumeration members have the same underlying value and you attempt to retrieve the string representation of an enumeration member's name based on its underlying value, your code should not make any assumptions about which name the method will return.
(Note that the decision is in the BCL - it's not a language decision.)
I suggest that if you want a canonical string representation for each value, you create a Dictionary<E, string> and consult that rather than calling ToString().
Consider this alternative solution. You can decorate enum values with the DescriptionAttribute and to have a more human friendly name:
enum E
{
[System.ComponentModel.Description("FullNameForA")]
A = 1
}
Then you can extract the value of that attribute like so:
public static string AsString(this Enum value)
{
var type = value.GetType();
if (!type.IsEnum)
throw new ArgumentException();
var fieldInfo = type.GetField(value.ToString());
if (fieldInfo == null)
return value.ToString();
var attribs = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
return attribs.Length > 0 ? attribs[0].Description : value.ToString();
}
This of course isn't the best performing solution because it relies on reflection.

Categories

Resources