How could I implement the .NET 4's Enum.TryParse method in .NET 3.5?
public static bool TryParse<TEnum>(string value, out TEnum result) where TEnum : struct
I dislike using a try-catch to handle any conversion failures or other non-exceptional events as part of the normal flow of my application, so my own Enum.TryParse method for .NET 3.5 and earlier makes use of the Enum.IsDefined() method to make sure the there will not be an exception thrown by Enum.Parse(). You can also include some null checks on value to prevent an ArgumentNullException if value is null.
public static bool TryParse<TEnum>(string value, out TEnum result)
where TEnum : struct, IConvertible
{
var retValue = value == null ?
false :
Enum.IsDefined(typeof(TEnum), value);
result = retValue ?
(TEnum)Enum.Parse(typeof(TEnum), value) :
default(TEnum);
return retValue;
}
Obviously this method will not reside in the Enum class so you will need a class to include this in that would be appropriate.
One limitation is the lack of an enum constraint on generic methods, so you would have to consider how you want to handle incorrect types. Enum.IsDefined will throw an ArgumentException if TEnum is not an enum but the only other option is a runtime check and throwing a different exception, so I generally do not add an additional check and just let the type checking in these methods handle for me. I'd consider adding IConvertible as another constraint, just to help constrain the type even more.
Took longer than I hoped to get this right, but it works and has been tested. Hope this saves someone some time!
private static readonly char[] FlagDelimiter = new [] { ',' };
public static bool TryParseEnum<TEnum>(string value, out TEnum result) where TEnum : struct {
if (string.IsNullOrEmpty(value)) {
result = default(TEnum);
return false;
}
var enumType = typeof(TEnum);
if (!enumType.IsEnum)
throw new ArgumentException(string.Format("Type '{0}' is not an enum", enumType.FullName));
result = default(TEnum);
// Try to parse the value directly
if (Enum.IsDefined(enumType, value)) {
result = (TEnum)Enum.Parse(enumType, value);
return true;
}
// Get some info on enum
var enumValues = Enum.GetValues(enumType);
if (enumValues.Length == 0)
return false; // probably can't happen as you cant define empty enum?
var enumTypeCode = Type.GetTypeCode(enumValues.GetValue(0).GetType());
// Try to parse it as a flag
if (value.IndexOf(',') != -1) {
if (!Attribute.IsDefined(enumType, typeof(FlagsAttribute)))
return false; // value has flags but enum is not flags
// todo: cache this for efficiency
var enumInfo = new Dictionary<string, object>();
var enumNames = Enum.GetNames(enumType);
for (var i = 0; i < enumNames.Length; i++)
enumInfo.Add(enumNames[i], enumValues.GetValue(i));
ulong retVal = 0;
foreach(var name in value.Split(FlagDelimiter)) {
var trimmedName = name.Trim();
if (!enumInfo.ContainsKey(trimmedName))
return false; // Enum has no such flag
var enumValueObject = enumInfo[trimmedName];
ulong enumValueLong;
switch (enumTypeCode) {
case TypeCode.Byte:
enumValueLong = (byte)enumValueObject;
break;
case TypeCode.SByte:
enumValueLong = (byte)((sbyte)enumValueObject);
break;
case TypeCode.Int16:
enumValueLong = (ushort)((short)enumValueObject);
break;
case TypeCode.Int32:
enumValueLong = (uint)((int)enumValueObject);
break;
case TypeCode.Int64:
enumValueLong = (ulong)((long)enumValueObject);
break;
case TypeCode.UInt16:
enumValueLong = (ushort)enumValueObject;
break;
case TypeCode.UInt32:
enumValueLong = (uint)enumValueObject;
break;
case TypeCode.UInt64:
enumValueLong = (ulong)enumValueObject;
break;
default:
return false; // should never happen
}
retVal |= enumValueLong;
}
result = (TEnum)Enum.ToObject(enumType, retVal);
return true;
}
// the value may be a number, so parse it directly
switch (enumTypeCode) {
case TypeCode.SByte:
sbyte sb;
if (!SByte.TryParse(value, out sb))
return false;
result = (TEnum)Enum.ToObject(enumType, sb);
break;
case TypeCode.Byte:
byte b;
if (!Byte.TryParse(value, out b))
return false;
result = (TEnum)Enum.ToObject(enumType, b);
break;
case TypeCode.Int16:
short i16;
if (!Int16.TryParse(value, out i16))
return false;
result = (TEnum)Enum.ToObject(enumType, i16);
break;
case TypeCode.UInt16:
ushort u16;
if (!UInt16.TryParse(value, out u16))
return false;
result = (TEnum)Enum.ToObject(enumType, u16);
break;
case TypeCode.Int32:
int i32;
if (!Int32.TryParse(value, out i32))
return false;
result = (TEnum)Enum.ToObject(enumType, i32);
break;
case TypeCode.UInt32:
uint u32;
if (!UInt32.TryParse(value, out u32))
return false;
result = (TEnum)Enum.ToObject(enumType, u32);
break;
case TypeCode.Int64:
long i64;
if (!Int64.TryParse(value, out i64))
return false;
result = (TEnum)Enum.ToObject(enumType, i64);
break;
case TypeCode.UInt64:
ulong u64;
if (!UInt64.TryParse(value, out u64))
return false;
result = (TEnum)Enum.ToObject(enumType, u64);
break;
default:
return false; // should never happen
}
return true;
}
It won't be a static method on Enum (static extension methods don't quite make sense), but it should work
public static class EnumHelpers
{
public static bool TryParse<TEnum>(string value, out TEnum result)
where TEnum : struct
{
try
{
result = (TEnum)Enum.Parse(typeof(TEnum), value);
}
catch
{
result = default;
return false;
}
return true;
}
}
At NLog we also needed Enum.TryParse for .Net 3.5. We have implemented the basic features (just parse, case sensitive and insensitive, no flags) influenced by this post.
This basic implementation is highly unit tested so it has the same behavior as Microsoft`s .Net 4 implementation.
/// <summary>
/// Enum.TryParse implementation for .net 3.5
///
/// </summary>
/// <returns></returns>
/// <remarks>Don't uses reflection</remarks>
// ReSharper disable once UnusedMember.Local
private static bool TryParseEnum_net3<TEnum>(string value, bool ignoreCase, out TEnum result) where TEnum : struct
{
var enumType = typeof(TEnum);
if (!enumType.IsEnum())
throw new ArgumentException($"Type '{enumType.FullName}' is not an enum");
if (StringHelpers.IsNullOrWhiteSpace(value))
{
result = default(TEnum);
return false;
}
try
{
result = (TEnum)Enum.Parse(enumType, value, ignoreCase);
return true;
}
catch (Exception)
{
result = default(TEnum);
return false;
}
}
And is using:
public static class StringHelpers
{
/// <summary>
/// IsNullOrWhiteSpace, including for .NET 3.5
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
[ContractAnnotation("value:null => true")]
internal static bool IsNullOrWhiteSpace(string value)
{
#if NET3_5
if (value == null) return true;
if (value.Length == 0) return true;
return String.IsNullOrEmpty(value.Trim());
#else
return string.IsNullOrWhiteSpace(value);
#endif
}
}
The code can be found at the NLog GitHub, and also the unit tests are on GitHub (xUnit)
Related
I receive some not really ISO conform Json content from an api. The boolean values are uppercase instead of lower case.
{ "Bool": False }
Initially, I thought that should be easy to solve by using a custom JsonConverter like shown in how to get newtonsoft to deserialize yes and no to boolean.
But it looks like the JsonConverter.ReadJson method is never called. I think the reason is, that the value False is not in quotes and thus JsonTextReader never calls the converter and creates the exception.
What would be the best way to handle that scenario?
public class BoolTests
{
public class A
{
[JsonConverter(typeof(CaseIgnoringBooleanConverter))]
public bool Bool { get; set; }
}
[Theory]
[InlineData(false, "{'Bool': false}")] //ok
[InlineData(false, "{'Bool': 'False'}")] // ok
[InlineData(false, "{'Bool': False")] // fails
public void CasingMatters(bool expected, string json)
{
var actual = JsonConvert.DeserializeObject<A>(json);
Assert.Equal(expected, actual.Bool);
}
}
// taken from https://gist.github.com/randyburden/5924981
public class CaseIgnoringBooleanConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
switch (reader.Value.ToString().ToUpperInvariant().Trim())
{
case "TRUE":
return true;
case "FALSE":
return false;
}
// If we reach here, we're pretty much going to throw an error so let's let Json.NET throw it's pretty-fied error message.
return new JsonSerializer().Deserialize(reader, objectType);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(bool);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Unfortunately, as you've discovered, invalid json is invalid, and thus not handled by normal and common json (de)serializers, such as Json.net.
Using converters and strategy settings for the deserializers will not work either as they're meant to handle things like empty-objects-returned-as-arrays or name conversion/case handling.
One naive solution would be to do a simple string replace, like
string json = invalidJson.Replace("False", "false");
This, however, has some problems:
You need to read the entire invalid json into memory, as well as create a fixed copy of it, which means you will have two entire copies of the data in memory, one bad and one better.
It would replace False inside strings as well. This may not be a problem with your data but wouldn't be easy to handle with the above approach.
A different approach would be to write a basic tokenizer that understands rudimentary JSON syntax, such as strings and numbers and identifiers, and go through the file token by token, replacing the bad identifiers. This would fix problem 2, but depending on the solution might need a more complex implementation to fix problem 1 with the memory.
A simple attempt at creating a TextReader that can be used, that will fix identifiers as they're found and otherwise understands rudimentary JSON tokens is posted below.
Note the following:
It is not really performant. It allocates temporary buffers all the time. You might want to look into "buffer renting" to handle this approach somewhat better, or even just stream directly to the buffer.
It doesn't handle numbers, because I stopped writing code at that point. I left this as an excercise. A basic number handling can be written because you're not really validating that the file is having valid JSON, so anything that will grab enough characters to constitute a number can be added.
I did not test this with really big files, only with the small example file. I replicated a List<Test> with 9.5MB of text, and it works for that.
I did not test all JSON syntax. There may be characters that should be handled but isn't. If you end up using this, create LOTS of tests!
What it does, however, is fix the invalid JSON according to the identifier(s) you've posted, and it does so in a streaming manner. This should thus be usable no matter how big a JSON file you have.
Anyway, here's the code, again note the exception regarding numbers:
void Main()
{
using (var file = File.OpenText(#"d:\temp\test.json"))
using (var fix = new MyFalseFixingTextReader(file))
{
var reader = new JsonTextReader(fix);
var serializer = new JsonSerializer();
serializer.Deserialize<Test>(reader).Dump();
}
}
public class MyFalseFixingTextReader : TextReader
{
private readonly TextReader _Reader;
private readonly StringBuilder _Buffer = new StringBuilder(32768);
public MyFalseFixingTextReader(TextReader reader) => _Reader = reader;
public override void Close()
{
_Reader.Close();
base.Close();
}
public override int Read(char[] buffer, int index, int count)
{
TryFillBuffer(count);
int amountToCopy = Math.Min(_Buffer.Length, count);
_Buffer.CopyTo(0, buffer, index, amountToCopy);
_Buffer.Remove(0, amountToCopy);
return amountToCopy;
}
private (bool more, char c) TryReadChar()
{
int i = _Reader.Read();
if (i < 0)
return (false, default);
return (true, (char)i);
}
private (bool more, char c) TryPeekChar()
{
int i = _Reader.Peek();
if (i < 0)
return (false, default);
return (true, (char)i);
}
private void TryFillBuffer(int count)
{
if (_Buffer.Length >= count)
return;
while (_Buffer.Length < count)
{
var (more, c) = TryPeekChar();
if (!more)
break;
switch (c)
{
case '{':
case '}':
case '[':
case ']':
case '\r':
case '\n':
case ' ':
case '\t':
case ':':
case ',':
_Reader.Read();
_Buffer.Append(c);
break;
case '"':
_Buffer.Append(GrabString());
break;
case char letter when char.IsLetter(letter):
var identifier = GrabIdentifier();
_Buffer.Append(ReplaceFaultyIdentifiers(identifier));
break;
case char startOfNumber when startOfNumber == '-' || (startOfNumber >= '0' && startOfNumber <= '9'):
_Buffer.Append(GrabNumber());
break;
default:
throw new InvalidOperationException($"Unable to cope with character '{c}' (0x{((int)c).ToString("x2")})");
}
}
}
private string ReplaceFaultyIdentifiers(string identifier)
{
switch (identifier)
{
case "False":
return "false";
case "True":
return "true";
case "Null":
return "null";
default:
return identifier;
}
}
private string GrabNumber()
{
throw new NotImplementedException("Left as an excercise");
// See https://www.json.org/ for the syntax
}
private string GrabIdentifier()
{
var result = new StringBuilder();
while (true)
{
int i = _Reader.Peek();
if (i < 0)
break;
char c = (char)i;
if (char.IsLetter(c))
{
_Reader.Read();
result.Append(c);
}
else
break;
}
return result.ToString();
}
private string GrabString()
{
_Reader.Read();
var result = new StringBuilder();
result.Append('"');
while (true)
{
var (more, c) = TryReadChar();
if (!more)
return result.ToString();
switch (c)
{
case '"':
result.Append(c);
return result.ToString();
case '\\':
result.Append(c);
(more, c) = TryReadChar();
if (!more)
return result.ToString();
switch (c)
{
case 'u':
result.Append(c);
for (int index = 1; index <= 4; index++)
{
(more, c) = TryReadChar();
if (!more)
return result.ToString();
result.Append(c);
}
break;
default:
result.Append(c);
break;
}
break;
default:
result.Append(c);
break;
}
}
}
}
public class Test
{
public bool False1 { get; set; }
public bool False2 { get; set; }
public bool False3 { get; set; }
}
Example file:
{
"false1": false,
"false2": "false",
"false3": False
}
Output (from LINQPad):
As said Lasse :
Invalid json should be fixed at the source.
If you really need to parse it as it is, you could replace False by "False" (as suggested by #Sinatr) if you want it as a string or false if you want it as a bool.
// If you want a string
json.Replace("False", "\"False\"");
// If you want a bool
json.Replace("False", "false");
One problem would be if a key or another value contains the "False" pattern.
I have the need to examine to see if an object can be converted to a specific DataType or not, and came up with this :
public static bool TryParseAll(System.Type typeToConvert, object valueToConvert)
{
bool succeed = false;
switch (typeToConvert.Name.ToUpper())
{
case "DOUBLE":
double d;
succeed = double.TryParse(valueToConvert.ToString(), out d);
break;
case "DATETIME":
DateTime dt;
succeed = DateTime.TryParse(valueToConvert.ToString(), out dt);
break;
case "INT16":
Int16 i16;
succeed = Int16.TryParse(valueToConvert.ToString(), out i16);
break;
case "INT":
Int32 i32;
succeed = Int32.TryParse(valueToConvert.ToString(), out i32);
break;
case "INT32":
Int32 i322;
succeed = Int32.TryParse(valueToConvert.ToString(), out i322);
break;
case "INT64":
Int64 i64;
succeed = Int64.TryParse(valueToConvert.ToString(), out i64);
break;
case "BOOLEAN":
bool b;
succeed = Boolean.TryParse(valueToConvert.ToString(), out b);
break;
case "BOOL":
bool b1;
succeed = bool.TryParse(valueToConvert.ToString(), out b1);
break;
}
return succeed;
}
I'm wondering is there any ways other than this? Which is more dynamic and more efficient?
Thanks!
You should use the TypeDescriptor class:
public static T Convert<T>(this string input)
{
var converter = TypeDescriptor.GetConverter(typeof(T));
if(converter != null)
{
//Cast ConvertFromString(string text) : object to (T)
return (T)converter.ConvertFromString(input);
}
return default(T);
}
of course this will throw an exception if the conversion fails so you will want to try/catch it.
Here is my version of generic TryParse method.
I believe you can use this version too:
double pi;
if(ValueTypeHelper.TryParse("3.14159", out pi)) {
// .. pi = 3.14159
}
...
string eStr = "2.71828";
float e;
if(eStr.TryParse(out e)) {
// .. e = 2.71828f
}
...
static class ValueTypeHelper {
static IDictionary<Type, Delegate> cache = new Dictionary<Type, Delegate>();
public static bool TryParse<T>(this string valueStr, out T result) {
Delegate d = null;
if(!cache.TryGetValue(typeof(T), out d)) {
var mInfos = typeof(T).GetMember("TryParse", MemberTypes.Method, BindingFlags.Static | BindingFlags.Public);
if(mInfos.Length > 0) {
var s = Expression.Parameter(typeof(string));
var r = Expression.Parameter(typeof(T).MakeByRefType());
d = Expression.Lambda<TryParseDelegate<T>>(
Expression.Call(mInfos[0] as MethodInfo, s, r), s, r).Compile();
}
cache.Add(typeof(T), d);
}
result = default(T);
TryParseDelegate<T> tryParse = d as TryParseDelegate<T>;
return (tryParse != null) && tryParse(valueStr, out result);
}
delegate bool TryParseDelegate<T>(string valueStr, out T result);
}
I have combined both DmitryG's and RezaRahmati's suggested solutions:
static class GenericValueConverter
{
public static bool TryParse<T>(this string input, out T result)
{
bool isConversionSuccessful = false;
result = default(T);
var converter = TypeDescriptor.GetConverter(typeof(T));
if (converter != null)
{
try
{
result = (T)converter.ConvertFromString(input);
isConversionSuccessful = true;
}
catch { }
}
return isConversionSuccessful;
}
}
void Main()
{
double pi;
if (GenericValueConverter.TryParse("3,14159", out pi)) //Use right decimal point seperator for local culture
{
pi.Dump(); //ConsoleWriteline for LinqPad
//pi=3,14159
}
string dtStr = "2016-12-21T16:34:22";
DateTime dt;
if (dtStr.TryParse(out dt))
{
dt.Dump(); //ConsoleWriteline for LinqPad
//dt=21.12.2016 16:34:22
}
string guidStr = "D430831B-03B0-44D5-A971-4E73AF96B5DF";
Guid guid;
if (guidStr.TryParse(out guid))
{
guid.Dump(); //ConsoleWriteline for LinqPad
//guid=d430831b-03b0-44d5-a971-4e73af96b5df
}
}
I'd like to know if there is a "safe" way to convert an object to an int, avoiding exceptions.
I'm looking for something like public static bool TryToInt32(object value, out int result);
I know I could make something like this:
public static bool TryToInt32(object value, out int result)
{
try
{
result = Convert.ToInt32(value);
return true;
}
catch
{
result = 0;
return false;
}
}
But I'd rather avoid exceptions, because they are slowing down the process.
I think this is more elegant, but it's still "cheap":
public static bool TryToInt32(object value, out int result)
{
if (value == null)
{
result = 0;
return false;
}
return int.TryParse(value.ToString(), out result);
}
Does anyone have better ideas?
UPDATE:
This sounds a little like splitting hairs, but converting an object to string forces the implementer to create a clear ToString() function. For example:
public class Percentage
{
public int Value { get; set; }
public override string ToString()
{
return string.Format("{0}%", Value);
}
}
Percentage p = new Percentage();
p.Value = 50;
int v;
if (int.TryParse(p.ToString(), out v))
{
}
This goes wrong, I can do two things here, or implement the IConvertable like this:
public static bool ToInt32(object value, out int result)
{
if (value == null)
{
result = 0;
return false;
}
if (value is IConvertible)
{
result = ((IConvertible)value).ToInt32(Thread.CurrentThread.CurrentCulture);
return true;
}
return int.TryParse(value.ToString(), out result);
}
But the ToInt32 method of the IConvertible cannot be canceled. So if it's not possible to convert the value, an exception cannot be avoided.
Or two: Is there a way to check if the object contains a implicit operator?
This is very poor:
if (value.GetType().GetMethods().FirstOrDefault(method => method.Name == "op_Implicit" && method.ReturnType == typeof(int)) != null)
{
result = (int)value;
return true;
}
int variable = 0;
int.TryParse(stringValue, out variable);
If it can't be parsed, the variable will be 0. See http://msdn.microsoft.com/en-us/library/f02979c7.aspx
Spurring from the comments. The response is no. You can't do what Convert.ToInt32(object) does without having throwed exceptions. You can do something similar (and you already did it). The only thing I would optimize is the case of value already an int.
if (value is int)
return (int)value;
You can't do as Convert.ToInt32(object) because Convert.ToInt32(object) doesn't simply test if value is short, int, long, ushort, ... and then cast them. It checks if the value is IConvertible. If yes it uses the IConvertible.ToInt32. Sadly the interface IConvertible is quite poor: it doesn't have non-throwing methods (IConvertible.Try*)
While stupid (but perhaps not too much), someone could make for example a UnixDateTime struct: (UnixTime is the number of seconds from midnight 1970-01-01), where the IConvertible.ToInt32 returns this number of seconds, while the ToString() returns a formatted date. All the int.TryParse(value.ToString(), out parsed) would choke, while the Convert.ToInt32 would work flawlessly.
This version using a type converter would only convert to string as a last resort but also not throw an exception:
public static bool TryToInt32(object value, out int result)
{
if (value == null)
{
result = 0;
return false;
}
var typeConverter = System.ComponentModel.TypeDescriptor.GetConverter(value);
if (typeConverter != null && typeConverter.CanConvertTo(typeof(int)))
{
var convertTo = typeConverter.ConvertTo(value, typeof(int));
if (convertTo != null)
{
result = (int)convertTo;
return true;
}
}
return int.TryParse(value.ToString(), out result);
}
No need to re-invent the wheel here. use int.TryParse to achieve your goal. It returns a bool to show that value is parsed or not. and if parsed the result is saved in the output variable.
int result;
object a = 5;
if(int.TryParse(a.ToString(),out result))
{
Console.WriteLine("value is parsed"); //will print 5
}
object b = a5;
if(int.TryParse(b.ToString(),out result))
{
Console.WriteLine("value is parsed");
}
else
{
Console.WriteLine("input is not a valid integer"); //will print this
}
Return a nullable int. that way you know whether you parsed 0.
int? value = int.TryParse(stringValue, out int outValue)
? outValue
: default(int?);
I would use a mixture of what you are already doing;
Check if the object is null - return false and the value 0;
Attempt to convert directly - if successful, return true and the converted value
Attempt to parse value.ToString() - if successfull, return true and the parsed value
Any other case - Return false and the value 0, as object is not convertible/parsible
The resulting code:
public static bool TryToInt32(object value, out int result)
{
result = 0;
if (value == null)
{
return false;
}
//Try to convert directly
try
{
result = Convert.ToInt32(value);
return true;
}
catch
{
//Could not convert, moving on
}
//Try to parse string-representation
if (Int32.TryParse(value.ToString(), out result))
{
return true;
}
//If parsing also failed, object cannot be converted or paresed
return false;
}
I wrote this mess, looking at it makes me sad.
using System;
using System.Globalization;
internal static class ObjectExt
{
internal static bool TryConvertToDouble(object value, out double result)
{
if (value == null || value is bool)
{
result = 0;
return false;
}
if (value is double)
{
result = (double)value;
return true;
}
var text = value as string;
if (text != null)
{
return double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out result);
}
var convertible = value as IConvertible;
if (convertible == null)
{
result = 0;
return false;
}
try
{
result = convertible.ToDouble(CultureInfo.InvariantCulture);
return true;
}
catch (Exception)
{
result = 0;
return false;
}
}
}
Edit
Notice now I answered for double when the question was int, keeping it any way. Maybe useful for someone.
This is how I like to do it:
object v = someValue;
if (int.TryParse($"{v}", out var extractedValue))
{
// do something with extractedValue
}
How will a C# switch statement's default label handle a nullable enum?
Will the default label catch nulls and any unhandled cases?
If it's null, it will hit the default label.
public enum YesNo
{
Yes,
No,
}
public class Program
{
public static void Main(string[] args)
{
YesNo? value = null;
switch (value)
{
case YesNo.Yes:
Console.WriteLine("Yes");
break;
case YesNo.No:
Console.WriteLine("No");
break;
default:
Console.WriteLine("default");
break;
}
}
}
The program will print default.
Unless null is handled.
public class Program
{
public static void Main(string[] args)
{
YesNo? value = null;
switch (value)
{
case YesNo.Yes:
Console.WriteLine("Yes");
break;
case YesNo.No:
Console.WriteLine("No");
break;
case null:
Console.WriteLine("NULL");
break;
default:
Console.WriteLine("default");
break;
}
}
}
prints NULL.
If you have an unhandled enum value that was added later:
public enum YesNo
{
Yes,
No,
FileNotFound,
}
public class Program
{
public static void Main(string[] args)
{
YesNo? value = YesNo.FileNotFound;
switch (value)
{
case YesNo.Yes:
Console.WriteLine("Yes");
break;
case YesNo.No:
Console.WriteLine("No");
break;
default:
Console.WriteLine("default");
break;
}
}
}
It still prints default.
You can use the null-coalescing operator ?? to route null switch values to a specific case label other than default:
public static IEnumerable<String> AsStrings(this IEnumerable<Char[]> src)
{
Char[] rgch;
var e = src.GetEnumerator();
while (e.MoveNext())
{
switch ((rgch = e.Current)?.Length ?? -1)
{
case -1: // <-- value when e.Current is 'null'
yield return null;
break;
case 0:
yield return String.Empty;
break;
case 1:
yield return String.Intern(new String(rgch[0], 1));
break;
default: // 2...n
yield return new String(rgch);
break;
}
}
}
You can have a case for null.
switch (MyNullableEnum)
{
case Option1:
break;
case Option2:
break;
case Option3:
break;
case null:
break;
default:
break;
}
It's worth to mention that C# 8.0 introduced a new Property Pattern for a switch expression. Now you can implement default logic to switch by using underscore:
public double Calculate(int left, int right, Operator op) =>
op switch
{
Operator.PLUS => left + right,
Operator.MINUS => left - right,
Operator.MULTIPLY => left * right,
Operator.DIVIDE => left / right,
_ => 0 // default
}
Ref. https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8
Suppose enum:
public enum SysLogsAppTypes { None, MonitorService, MonitorTool };
and here is a function to convert from the ToString() representation back to enum:
private SysLogsAppTypes Str2SysLogsAppTypes(string str)
{
try
{
SysLogsAppTypes res = (SysLogsAppTypes)Enum
.Parse(typeof(SysLogsAppTypes), str);
if (!Enum.IsDefined(typeof(SysLogsAppTypes), res))
return SysLogsAppTypes.None;
return res;
}
catch
{
return SysLogsAppTypes.None;
}
}
Is there a way to make this Generic ??
I tried:
private T Str2enum<T>(string str)
{
try
{
T res = (T)Enum.Parse(typeof(T), str);
if (!Enum.IsDefined(typeof(T), res)) return T.None;
return res;
}
catch
{
return T.None;
}
}
but I get:
'T' is a 'type parameter', which is not valid in the given context
where there is T.None
Any help ?
Thanks
I think the default keyword is what you need:
private T Str2enum<T>(string str) where T : struct
{
try
{
T res = (T)Enum.Parse(typeof(T), str);
if (!Enum.IsDefined(typeof(T), res)) return default(T);
return res;
}
catch
{
return default(T);
}
}
Not the way you are trying it, but I use the method below to do this:
public static bool EnumTryParse<E>(string enumVal, out E resOut)
where E : struct
{
var enumValFxd = enumVal.Replace(' ', '_');
if (Enum.IsDefined(typeof(E), enumValFxd))
{
resOut = (E)Enum.Parse(typeof(E),
enumValFxd, true);
return true;
}
// ----------------------------------------
foreach (var value in
Enum.GetNames(typeof (E)).Where(value =>
value.Equals(enumValFxd,
StringComparison.OrdinalIgnoreCase)))
{
resOut = (E)Enum.Parse(typeof(E), value);
return true;
}
resOut = default(E);
return false;
}
No exceptions thrown here ...
I like to add in a defaultValue parameter for an overload of my TryParse for cases where I want a default if it can't be parsed or is null. This is most useful for parsing string.Empty or null.
Note: this implementation will revert to defaultValue if a junk value is passed in - so you may want to tweak that by throwing an exception.
public static T TryParse<T>(string value, T defaultValue) where T: struct
{
if (string.IsNullOrWhiteSpace(value))
{
return defaultValue;
}
T result;
if (Enum.TryParse<T>(value, out result))
{
return result;
}
else
{
return defaultValue; // you may want to throw exception here
}
}
}
ConverterMode mode = EnumUtils<ConverterMode>.TryParse(stringValue, ConverterMode.DefaultMode);
I know this is old, but based on a few samples, I've researched along with #Simon_Weaver's solution, this is what I have:
public static T TryParse(String value, T defaultValue) where T : struct {
if (String.IsNullOrWhiteSpace(value)) {
return defaultValue;
}
T result;
if (!Enum.TryParse(value, out result)) {
if (Enum.IsDefined(typeof (T), result) | result.ToString().Contains(",")) {
// do nothing
} else {
result = defaultValue;
}
} else {
result = defaultValue;
}
return result;
}