Enum.TryParse strange behaviour - c#

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

Related

Is a value an enum and -1?

For view model validation, I need to determine whether a value, of which I only have an object interface, is an enum and has the numeric value of -1.
I tried this:
// object value;
if (value?.GetType().IsEnum == true && (int)value == -1) return null;
It should work with my Model enums which are mostly based on int.
But it fails when the value is a Visibility enum (that happens to be also in the view model class and should be ignored for validation) which is based on byte instead of int and that seems not to be castable to int. I could do some more testing but it shouldn't get too slow.
Is there a good simple solution for that? Maybe some test method in the Enum class or something?
You can check the underlying type with GetEnumUnderlyingType():
Type t = value?.GetType();
if (t?.IsEnum == true &&
t?.GetEnumUnderlyingType() == typeof(int) &&
(int)value == -1)
return null;
Since a byte can never be -1, you don't need to check it. But you may need to extend the check for long enums, too.
UPDATE:
I just tried around a little and found that Convert.ToInt32() also solves your problem:
if (value?.GetType().IsEnum == true &&
Convert.ToInt64(value) == -1)
return null;
This seems cleaner and also works for all possible underlying types.
Another update: Unfortunatly the solution above is not as clean as I thought. Even with Convert.ToInt64() solves the problem of long values too big for Int32, but it throws if you pass for example a ulong.MaxValue.
So you have to choose a type that is large enough for all possible enum base types:
if (value?.GetType().IsEnum == true &&
Convert.ToDecimal(value) == -1)
return null;
Using Convert.ToDecimal() this passes all the test cases that came up so far.
In addition, one could consider using the Enum.IsDefined Method (Type, Object) to verify, if the value is a valid enum.
That does of course not cover the "check if it is -1" part.
Enum.IsDefined Method (Type, Object)
Returns an indication whether a constant with a specified value exists
in a specified enumeration. Namespace: System Assembly: mscorlib
(in mscorlib.dll)
[ComVisibleAttribute(true)] public static bool IsDefined( Type enumType, object value )
Parameters
enumTypeType: System.Type
An enumeration type.
valueType: System.Object The value or name of a constant in enumType.
Return Value
Type: System.Boolean becomes true if a constant in enumType has a
value equal to value; otherwise, false.
Exceptions
ArgumentNullException' enumType or value is null.
ArgumentException` enumType is not an Enum. -or- The type of value is an enumeration, but it is not an enumeration of type enumType. -or- The type of value is not an underlying type of enumType.
InvalidOperationException value is not type SByte, Int16, Int32, Int64, Byte, UInt16, UInt32, UInt64, or String.
Remarks
The value parameter can be any of the following:
Any member of type enumType.
A variable whose value is an enumeration member of type enumType.
The string representation of the name of an enumeration member. The
characters in the string must have the same case as the enumeration
member name.
A value of the underlying type of enumType.
If the constants in enumType define a set of bit fields and value
contains the values, names, or underlying values of multiple bit
fields, the IsDefined method returns false. In other words, for
enumerations that define a set of bit fields, the method determines
only whether a single bit field belongs to the enumeration. To
determine whether multiple bit fields are set in an enumeration type
that is tagged with the FlagsAttribute attribute, you can call the
HasFlag method.
You could:
if (value != null)
{
var type = value.GetType();
if (type.IsEnum && object.Equals(Enum.ToObject(type, -1), value))
{
return null;
}
}
I had to do it in multiple lines to "cache" the GetType(). The trick is using Enum.ToObject() to "cast" -1 to the current Enum type.
Note that casting will generate "strange" results:
public enum TestEnum : ulong
{
ValueMax = ulong.MaxValue
}
In this case, TestEnum.ValueMax will return null. This will happen for all the unsigned types.
byte can be casted to int if it is not boxed with object
byte b = 1;
int i = (int)b; //works good
int f = (int)(object)b; //fails
So you could convert your variable to int using Convert.ToInt32 method, as René Vogt suggested, or cast it to dynamic instead casting to int:
if (value?.GetType().IsEnum == true && (dynamic)value == -1) return null;
Although, operations with dynamic are rather slow. In my opinion, soultion with Convert.ToInt32 is the most cleanable and efficient. This answer is just to point out, why you can not cast the object to int and suggest a dynamic version of cast.

Is it possible in .Net to set integer enum to arbitrary value

For some reason the HttpResponseMessageProperty which I'm using to return specific HTTP response codes to a client uses a HttpStatusCode enumeration. This enumeration does not include 422, which is not included in the enum. Is there any way to set an enumeration using an integer since that's what it is under the covers?
Yes, it's possible - internally an enum value is just an integer and you can "force" an enum value to have an invalid value and the CLR will continue on its merry way.
for example:
enum Foo {
Bar = 1,
Baz = 2
}
void Test() {
Foo foo; // as Foo is a value-type and no initial value is set, its value is zero.
Console.WriteLine( foo ); // will output "0" even though there is no enum member defined for that value
Foo foo2 = (Foo)200;
Console.WriteLine( foo2 ); // will output "200"
}
This is why, when working with enum values of unknown provenance, you should always handle the default case and handle appropriately:
public void DoSomethingWithEnum(Foo value) {
switch( value ) {
case Foo.Bar:
// do something
break;
case Foo.Baz:
// do something else
break;
default:
// I like to throw this exception in this circumstance:
throw new InvalidEnumArgumentException( "value", (int)value, typeof(Foo) );
}
}
You can cast any number into an enum, given the enum has the same base structure(usually it is int :
response.StatusCode = (HttpStatusCode)422;
Yes, you can have any value you want (as long as it is in range of enum's base type wich usally is int):
HttpStatusCode value = (HttpStatusCode)422;

TryParse of Enum is working, but I think it shouldn't [duplicate]

This question already has answers here:
Enum.TryParse returns true for any numeric values
(3 answers)
Closed 8 years ago.
I have the following Enum (which is generated from a xsd):
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")]
[System.SerializableAttribute()]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://www.ebutilities.at/invoice/02p00")]
[System.Xml.Serialization.XmlRootAttribute("Sector", Namespace = "http://www.ebutilities.at/invoice/02p00", IsNullable = false)]public enum
SectorType
{
[System.Xml.Serialization.XmlEnumAttribute("01")]
Item01,
[System.Xml.Serialization.XmlEnumAttribute("02")]
Item02,
[System.Xml.Serialization.XmlEnumAttribute("03")]
Item03,
[System.Xml.Serialization.XmlEnumAttribute("04")]
Item04,
[System.Xml.Serialization.XmlEnumAttribute("05")]
Item05,
[System.Xml.Serialization.XmlEnumAttribute("06")]
Item06,
[System.Xml.Serialization.XmlEnumAttribute("07")]
Item07,
[System.Xml.Serialization.XmlEnumAttribute("08")]
Item08,
[System.Xml.Serialization.XmlEnumAttribute("09")]
Item09,
[System.Xml.Serialization.XmlEnumAttribute("99")]
Item99,
}
So I want to parse a string to SectorType:
string s = "88";
SectorType sectorType;
bool result = Enum.TryParse(s, out sectorType);
After this my sectorType is "88" and result is true. So the conversion succeeded. Also this is working fine:
SectorType sectorType = (SectorType)Enum.Parse(typeof (SectorType), "88")
Value of sectorType is 88.
Here's a picture from the debugger:
MSDN provides following information:
Enum.TryParse Method
Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object. The return value indicates whether the conversion succeeded.
Obviously there is no equivalent enumerated object (88 (or whatever number) != Item01,..., Item09, Item99).
I was thinking Enums are strongly-typed (see dotnetperls/enums).
It says:
We see that enums are strongly-typed. You cannot assign them to just any value.
But clearly in my example, i could assign any number to my SectorType-Enum and I really don't know why this is working...
See it running at .NET Fiddle.
From the MSDN page for Enum.TryParse<TEnum>(string value, ...):
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.

Generic Enum Flag Parser

I have an array of string values that I would like to have set flags on a Flags Enum object. I have several of these and was looking for a more generic way to pass in the type of the Flags enum and not have to duplicate the method for each Flags Enum type. This is what I have currently:
public static MyFlagsEnum ParseFlagsEnum(string[] values)
{
MyFlagsEnum flags = new MyFlagsEnum();
foreach (var flag in values)
{
flags |= (MyFlagsEnum)Enum.Parse(typeof(MyFlagsEnum), flag);
}
return flags;
}
I was looking for a more generic way of doing the same thing, while being able to use any Flags Enum type.
Enum.Parse can already combine multiple flags. Quoting from the documentation:
Remarks
The value parameter contains the string representation of an enumeration member's underlying value or named constant, or a list of named constants delimited by commas (,). One or more blank spaces can precede or follow each value, name, or comma in value. If value is a list, the return value is the value of the specified names combined with a bitwise OR operation.
So you could do it like this:
public static T ParseFlagsEnum<T>(string[] values)
{
return (T)Enum.Parse(typeof(T), string.Join(",", values));
}

Enum helper in c# not giving expected result

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.

Categories

Resources