|
you da man Richard !
«One day it will have to be officially admitted that what we have christened reality is an even greater illusion than the world of dreams.» Salvador Dali
|
|
|
|
|
@RichardDeeming ... Hi Richard, fyi: in the code I posted on QA [^] I do check to make sure the Type is 'Enum.:
public static TEnum ToEnum<TEnum>(this Type tenum, string str, bool ignorecase = false)
where TEnum : struct, IConvertible
{
if (! tenum.IsEnum)
throw new ArgumentException("TEnum must be an Enum type");
TEnum result;
if (Enum.TryParse<TEnum>(str, ignorecase, out result))
{
return result;
}
throw new ArgumentException($"{str} is not a valid string for conversion to {tenum.FullName}");
} Richard Deeming wrote: However, I'm not too keen on having to pass the enum type twice. It's not clear from the signature which type will be used - the generic type parameter, or the tenum argument. I'm uncomfortable with that syntax as well.
To make that more bomb-proof:
public static TEnum ToEnum<TEnum>(this Type tenum, string str, bool ignorecase = false)
where TEnum : struct, IConvertible
{
Type returntype = typeof(TEnum);
if (!returntype.IsEnum)
throw new ArgumentException("Return Type must be an Enum type");
if (!tenum.IsEnum)
throw new ArgumentException("Type parameter must be an Enum type");
if (tenum != returntype)
throw new ArgumentException("Type and Return type must be the same");
TEnum result;
if (Enum.TryParse<TEnum>(str, ignorecase, out result))
{
return result;
}
throw new ArgumentException($"{str} is not a valid string for conversion to {tenum.FullName}");
} Ideally, we'd have a way for these edge cases to not even compile, but, ideally it would not be 104F (feels like 110F) in Thailand right now, and my air-con would not be wimping out on me
«One day it will have to be officially admitted that what we have christened reality is an even greater illusion than the world of dreams.» Salvador Dali
modified 21-Apr-20 4:28am.
|
|
|
|
|
How about:
public static TEnum ToEnum<TEnum>(this Type tenum, string str, bool ignoreCase = false)
where TEnum : struct, Enum
{
#if DEBUG
if (tenum != typeof(TEnum))
throw new ArgumentException("Type {tenum.FullName} does not match return type {typeof(TEnum).FullName}");
#endif
if (Enum.TryParse<TEnum>(str, ignoreCase, out TEnum result))
{
return result;
}
throw new ArgumentException($"'{str}' is not a valid string for conversion to {typeof(TEnum).FullName}");
} The Enum constraint means you don't need to check whether TEnum is an Enum type; anything else would be a compiler error.
The tenum parameter isn't really used, so it probably makes sense to omit the comparison in a release build.
And if we're going to throw an exception if tenum is not the same as typeof(TEnum) , we don't really need to test whether tenum is an Enum type first.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
food for thought, thanks ! @RichardDeeming
today, i considered what it would mean to write a factory method to return a hard-coded converter ... what i came up with is rather monstrous but it works:
var econverter = WeekDays.Default.MakeConverter<WeekDays>();
WeekDays wkd = econverter("monday", false); can't say i see enduring value for such elaboration to achieve a limited result. i added a "Default" value to the Enum:
public static Func<string, bool, TEnum> MakeConverter<TEnum>(this Enum tenum)
where TEnum : struct, IConvertible
{
Type returntype = typeof(TEnum);
if (!returntype.IsEnum)
throw new ArgumentException("Return type must be an Enum type");
Type argtype = tenum.GetType();
if (! argtype.IsEnum)
throw new ArgumentException("Type parameter must be an Enum type");
if (argtype != returntype)
throw new ArgumentException("Type and Return type must be the same");
Func<string, bool, TEnum> xFunc = null;
try
{
xFunc = (string str, bool ignorecase) =>
{
TEnum result;
if (Enum.TryParse<TEnum>(str, ignorecase, out result))
{
return result;
}
throw new ArgumentException(
$"{str} is not a valid string for conversion to {typeof(TEnum).FullName}");
};
}
catch (Exception ex)
{
throw new TypeInitializationException(typeof(TEnum).FullName, ex.InnerException);
}
return xFunc;
}
«One day it will have to be officially admitted that what we have christened reality is an even greater illusion than the world of dreams.» Salvador Dali
|
|
|
|
|
If you make the parameter TEnum , you won't need to check whether it's the same type as the type parameter, and you won't need to repeat the type when you call it.
public static Func<string, bool, TEnum> MakeConverter<TEnum>(this TEnum tenum) ...
var econverter = WeekDays.Default.MakeConverter(); I don't think you need to worry about getting an exception when you create the function - anything that's thrown then won't be recoverable anyway.
You can also let the compiler infer the parameter types for the returned Func<> .
public static Func<string, bool, TEnum> MakeConverter<TEnum>(this TEnum tenum)
where TEnum : struct, Enum
{
return (str, ignoreCase) =>
{
if (Enum.TryParse<TEnum>(str, ignoreCase, out var result)) return result;
throw new ArgumentException($"'{str}' is not a valid string for conversion to {typeof(TEnum).FullName}");
};
} With a throw expression[^], the converter function can come down to a single line:
return (str, ignoreCase) => Enum.TryParse<TEnum>(str, ignoreCase, out var result) ? result
: throw new ArgumentException($"'{str}' is not a valid string for conversion to {typeof(TEnum).FullName}"); You could even make the factory method an expression-bodied method, although I think that might be taking things a little too far:
public static Func<string, bool, TEnum> MakeConverter<TEnum>(this TEnum tenum) where TEnum : struct, Enum
=> (str, ignoreCase) => Enum.TryParse<TEnum>(str, ignoreCase, out var result) ? result
: throw new ArgumentException($"'{str}' is not a valid string for conversion to {typeof(TEnum).FullName}");
NB: If you want to maintain the optional parameter, you can't use the generic Func<> delegate; you'd need a custom delegate type:
public delegate TEnum EnumConverterDelegate<TEnum>(string str, bool ignoreCase = false) where TEnum : struct, Enum;
public static EnumConverterDelegate<TEnum> MakeConverter<TEnum>(this TEnum value) where TEnum : struct, Enum
=> (str, ignoreCase) => Enum.TryParse<TEnum>(str, ignoreCase, out var result) ? result
: throw new ArgumentException($"'{str}' is not a valid string for conversion to {typeof(TEnum).FullName}");
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
i think we're approaching apogee of our orbit around this planetoid
«One day it will have to be officially admitted that what we have christened reality is an even greater illusion than the world of dreams.» Salvador Dali
|
|
|
|
|
This might be simpler
using System;
namespace EnumConvert
{
public enum MyEnum
{
None = 0,
First,
Second,
Third,
Last
}
public static class EnumExtensions
{
public static Tenum ToEnum<Tenum>(this Tenum theEnum, string str, bool ignoreCase = false)
where Tenum : struct, Enum
{
if (Enum.TryParse<Tenum>(str, ignoreCase, out Tenum result))
return result;
throw new ArgumentException($"{str} is not a valid string for conversion to {typeof(Tenum).FullName}");
}
}
class Program
{
static void Main(string[] args)
{
MyEnum myEnum = new MyEnum();
var none = myEnum.ToEnum("None");
Console.WriteLine($"'None' results in {none} with a value of {(int)none}");
var first = myEnum.ToEnum("First");
Console.WriteLine($"'First' results in {first} with a value of {(int)first}");
var last = myEnum.ToEnum("last", true);
Console.WriteLine($"'last' results in {last} with a value of {(int)last}");
try
{
last = myEnum.ToEnum("last", false);
Console.WriteLine($"'last' results in {last} with a value of {(int)last}");
}
catch
{
Console.WriteLine("Error trying to convert 'last' to MyEnum");
}
try
{
var random = myEnum.ToEnum("Random", true);
Console.WriteLine($"'Random' results in {random} with a value of {(int)random}");
}
catch
{
Console.WriteLine("Error trying to convert 'random' to MyEnum");
}
}
}
}
"Time flies like an arrow. Fruit flies like a banana."
modified 21-Apr-20 12:19pm.
|
|
|
|
|
thanks !
MyEnum myEnum = new MyEnum(); for me, using 'new on an enum to get a default value makes the code even more obscure. i view that language "feature" as a flaw, given the special structure of enums, and their special behaviors.
«One day it will have to be officially admitted that what we have christened reality is an even greater illusion than the world of dreams.» Salvador Dali
modified 21-Apr-20 14:56pm.
|
|
|
|
|
Matthew Dennis wrote:
MyEnum myEnum = new MyEnum(); One slight simplification:
MyEnum myEnum = default;
MyEnum myEnum = 0;
var myEnum = default(MyEnum); default value expressions - C# reference | Microsoft Docs[^]
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
something about using 'new on an enum ... gives me a creepy feeling
«One day it will have to be officially admitted that what we have christened reality is an even greater illusion than the world of dreams.» Salvador Dali
|
|
|
|
|
Ah yes, I agree. The problem is that we were trying to put the extension on the wrong type. It should be and extension of string.
using System;
namespace EnumConvert
{
public enum MyEnum
{
None = 0,
First,
Second,
Third,
Last
}
public static class EnumExtensions
{
public static Tenum ToEnum<Tenum>(this string str, bool ignoreCase = false)
where Tenum : struct, Enum
{
if (Enum.TryParse<Tenum>(str, ignoreCase, out Tenum result))
return result;
throw new ArgumentException($"{str} is not a valid string for conversion to {typeof(Tenum).FullName}");
}
}
class Program
{
static void Main(string[] args)
{
var none =("None").ToEnum<MyEnum>();
Console.WriteLine($"'None' results in {none} with a value of {(int)none}");
var first = "First".ToEnum<MyEnum>();
Console.WriteLine($"'First' results in {first} with a value of {(int)first}");
var last = "last".ToEnum<MyEnum>(true);
Console.WriteLine($"'last' results in {last} with a value of {(int)last}");
try
{
last = "last".ToEnum<MyEnum>(false);
Console.WriteLine($"'last' results in {last} with a value of {(int)last}");
}
catch
{
Console.WriteLine("Error trying to convert 'last' to MyEnum");
}
try
{
var random = "Random".ToEnum<MyEnum>(true);
Console.WriteLine($"'Random' results in {random} with a value of {(int)random}");
}
catch
{
Console.WriteLine("Error trying to convert 'random' to MyEnum");
}
}
}
}
"Time flies like an arrow. Fruit flies like a banana."
|
|
|
|
|
Hi Matthew, in my post on QA [^], I published a method that extends 'string with the comment: Quote: imho, you do pay a price for either one: the first example extends string; I think extending basic types is generally a mistake. In the second example, you pay a price for the rather awkward structure of the calling format. i explored this more esoteric approach ... extending an Enum ... because i try to avoid extending basic types, like 'string ... a personal preference.
Glad to have your input, and hope to hear more from you, cheers, Bill
«One day it will have to be officially admitted that what we have christened reality is an even greater illusion than the world of dreams.» Salvador Dali
|
|
|
|
|
Extending primitive has no runtime penalty. Extension methods are a compiler syntactic sugar, so I use them a lot. The nice thing is that they are only visible if the dll containing them is included, and the file has the appropriate using statement, so only the parts of the app that need them have access to them. The intellisense list doesn't get unnecessarily polluted.
For example, public bool string.IsValidEmailAddress() is a nice extension.
"Time flies like an arrow. Fruit flies like a banana."
|
|
|
|
|
my only objection is that once you define an extension method for a basic type, putting a dot after any instance of that type at design time in VS will cause intellisense to present the method in its list. in the list of methods, the extension method is not indicated with any special visual adornment.
a more serious potential issues is the inherent 'public accessibility of extension methods ... if abused, this can violate encapsulation. you can address that, however, by the use of NameSpaces.
while there actually is a difference in the order in which extension methods are compiled: i see no reason to be concerned about that.
as i said, it's a personal preference. for a stronger opinion: [^]
«One day it will have to be officially admitted that what we have christened reality is an even greater illusion than the world of dreams.» Salvador Dali
|
|
|
|
|
minor quibbles: while I am aware of the newer features:
where T : Enum
use of 'default
they are not as simple to use as one might expect:
1. where T : struct, Enum ... fails without 'struct, and 'struct must precede 'Enum
2. 'default cannot be used directly ... WeekDays.Default throws an error ... however this will work:
((WeekDays) default).ToEnum<weekdays>("friday", true);
«One day it will have to be officially admitted that what we have christened reality is an even greater illusion than the world of dreams.» Salvador Dali
|
|
|
|
|
BillWoodruff wrote: 1. where T : struct, Enum ... fails without 'struct, and 'struct must precede 'Enum
Yes, for some odd reason an Enum constraint doesn't automatically imply a struct constraint.
As a result, it's possible to use where TEnum : class, Enum , which will compile but can never be satisfied.
But it's still better than not having the constraint.
BillWoodruff wrote: 2. 'default cannot be used directly ... WeekDays.Default throws an error
The default literal can be assigned to a variable of any type, or used as the default value of an optional parameter of any type.
It doesn't add a new member to the enum, so WeekDays.default won't work if the WeekDays enum doesn't already contain a default entry.
If you want to use the default value without assigning to a variable, you can use default(WeekDays) instead.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
reading between the lines, i almost had the feeling you thought theses comments were relevant
«One day it will have to be officially admitted that what we have christened reality is an even greater illusion than the world of dreams.» Salvador Dali
|
|
|
|
|
I have a couple of static methods that attempt to establish the correct enum value from the specified string (or int). I use them when loading data from a source that could potentially be corrupted by manual modifications by fat-fingered idiot users.
public static T IntToEnum<T>(int value, T defaultValue)
{
T enumValue = (Enum.IsDefined(typeof(T), value)) ? (T)(object)value : defaultValue;
return enumValue;
}
public static T StringToEnum<T>(string value, T defaultValue)
{
T enumValue = (Enum.IsDefined(typeof(T), value)) ? (T)Enum.Parse(typeof(T), value) : defaultValue;
return enumValue;
}
".45 ACP - because shooting twice is just silly" - JSOP, 2010 ----- You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010 ----- When you pry the gun from my cold dead hands, be careful - the barrel will be very hot. - JSOP, 2013
|
|
|
|
|
i would not use Enum.IsDefined: [^], [^]
«One day it will have to be officially admitted that what we have christened reality is an even greater illusion than the world of dreams.» Salvador Dali
|
|
|
|
|
I've never had a problem with that code.
It should return false if the spelling/case is wrong.
If it makes you feel more comfortable, you could change the code to use enum.TryParse instead of enum.Parse .
".45 ACP - because shooting twice is just silly" - JSOP, 2010 ----- You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010 ----- When you pry the gun from my cold dead hands, be careful - the barrel will be very hot. - JSOP, 2013
modified 22-Apr-20 13:59pm.
|
|
|
|
|
I share information with no expectation anyone will use it. Since this is a language discussion forum, not QA, I believe "going deep" is appropriate.
«One day it will have to be officially admitted that what we have christened reality is an even greater illusion than the world of dreams.» Salvador Dali
modified 23-Apr-20 4:20am.
|
|
|
|
|
I wasn't complaining.
".45 ACP - because shooting twice is just silly" - JSOP, 2010 ----- You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010 ----- When you pry the gun from my cold dead hands, be careful - the barrel will be very hot. - JSOP, 2013
|
|
|
|
|
I didn't interpret your response as complaining ... my crystal ball can't read Texan minds
«One day it will have to be officially admitted that what we have christened reality is an even greater illusion than the world of dreams.» Salvador Dali
|
|
|
|
|
Does anyone know how to add the sas.dll to a c# app?
|
|
|
|
|
Yes.
The difficult we do right away...
...the impossible takes slightly longer.
|
|
|
|
|