I'm not really sure if I have the correct title for this Q. anyway I'm trying to parse a delimited string into a list of Enum elements:
public enum MyEnum { Enum1, Enum2, Enum3 }
Given an input of:
string s = "Enum2, enum3, Foo,";
I would like to output only the parts that exists in MyEnum
(ignoring the case):[MyEnum.Enum2, MyEnum.Enum3]
IEnumerable<MyEnum> sl =
s.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(a => { if (Enum.TryParse(a, true, out MyEnum e)) return e; else return nothing ??? })
How do I return "nothing" from the Select()
if TryParse()
fails?
I could do an ugly thing like:
IEnumerable<MyEnum> sl =
s.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Where(a => Enum.TryParse(a, true, out MyEnum dummy))
.Select(a => Enum.Parse(typeof(MyEnum), a, true));
Doing the parsing job twice for no good reason,
But I'm sure I'm missing something trivial.
How can this be done in an efficient and elegant way?
It's super simple if you use nullable values for the enum
:
IEnumerable<MyEnum> sl =
s
.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(a => { if (Enum.TryParse(a, true, out MyEnum e)) return (MyEnum?)e; else return null; })
.Where(x => x.HasValue)
.Select(x => x.Value);
You can even reduce it down further:
.Select(a => Enum.TryParse(a, true, out MyEnum e) ? (MyEnum?)e : null)
Or you could use SelectMany
and avoid the nullables:
IEnumerable<MyEnum> sl =
s
.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.SelectMany(a => Enum.TryParse(a, true, out MyEnum e) ? new[] { e } : new MyEnum[] { });
Even though the Enum.TryParse
do the heavy lifting it is sub-optimal. It can generate false-positive result.
For example, if your s
string looks like this:
string s = "Enum2, enum3, Foo, 4";
then Enum.TryParse
will able to convert Enum2
, enum3
and 4
to a valid MyEnum
instance.
In order to filter out 4
from the result set you have to call Enum.IsDefined
. Here is a sample how can you combine TryParse
and IsDefined
:
s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Where(value => Enum.TryParse<MyEnum>(value, ignoreCase: true, out var parsedValue) && Enum.IsDefined(typeof(MyEnum), parsedValue))
.Select(value => (MyEnum)Enum.Parse(typeof(MyEnum), value, ignoreCase: true))
.ToList();
You can designate the value -1
to be an invalid enum value thereby serving as a placeholder for any invalid strings and allow them to be subsequently excluded. Also, this avoids parsing the strings more than once.
var invalidEnum = (MyEnum) (-1);
var sl = s.Split(new char[] {',', ' '}, StringSplitOptions.RemoveEmptyEntries)
.Select(a =>
{
var isValid = Enum.TryParse(a, true, out MyEnum myEnum);
return isValid ? myEnum : invalidEnum;
})
.Where(a => a != invalidEnum);
Potentially -1
could already be used by the enum if explicitly defined (unlike in the original example where default minimum is zero (0
) so using -1
is ok). To minimize this risk the following values could be used instead of -1
:
var invalidEnum = (MyEnum) (int.MinValue);
// or
var invalidEnum = (MyEnum) (int.MaxValue);
If still some concern that these highly unlikely values being taken then could find the next available number not used by the enum and assign it as the invalid enum:
var invalidEnum = (MyEnum) int.MinValue;
foreach(var number in Enumerable.Range(int.MinValue, int.MaxValue))
{
if (!Enum.IsDefined(typeof(MyEnum), number))
{
invalidEnum = (MyEnum) number;
break;
}
}
You can use the LINQ aggregate method that can be used for cases like this:
var sl= s.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Aggregate(new List<MyEnum> (),(List<MyEnum> enums,string a)=>
{
if (Enum.TryParse(a, true, out MyEnum e))
{
enums.Add(e);
}
return enums;
});
After you have divided your string s into substrings, you only want to keep only the enum values of the substrings that are proper myEnums.
You already knew how to divide your original string into substrings:
string s = ...
var subStrings = .Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
Use a Select to try to parse each subString into a nullable MyEnum. If that fails, the nullable has no value; otherwise the nullable has the MyEnum value.
Remove the nullables that have no values, and Select the Values of the remaining ones:
var result = subStrings.Select(subString => new
{
if (Enum.TryParse(subString, true out MyEnum parsedEnum)
{
return new Nullable<MyEnum>(parsedEnum);
}
else
{
return new Nullable<MyEnum>(null);
}
})
.Where(nullableEnum => nullableEnum.HasValue);
.Select(nullableEnum => nullableEnum.Value);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With