This question already has answers here:
Enum ToString with user friendly strings
(25 answers)
Using [Display(Name = "X")] with an enum. Custom HtmlHelper in MVC3 ASP.Net
(3 answers)
Closed 9 years ago.
Tried setting up an enum that has spaces in attributes but was very hard so figured their might be an easy way to hack this with a string format or something since their is only one enum that I need that has spaces and I know exactly what it is. Any helping wiht adding a space to this string
public class Address
{
...blah...more class datatypes here...
public AddressType Type { get; set; } //AddressType is an enum
...blah....
}
if (Address.Type.ToString() == "UnitedStates")
{
**Add space between United and States**
}
If your enum entries are in camel case, you can insert a whitespace before the upper letter
string res = Regex.Replace("UnitedStates", "[A-Z]", " $0").Trim();
You can create your own ToString method on the enumeration using an extension method.
public static class AddressTypeExtensions
{
public static string ToMyString(this AddressType addressType)
{
if (addressType == AddressType.UnitedStates)
return "United States";
return addressType.ToString();
}
}
This is a neat trick I found yesterday (in 2009). I wonder why I never thought of it myself. In the .net framework there is no way how to control .ToString() for enumerations. To work around that an extension method can be created as well as an attribute to decorate the different values of the enumeration. Then we can write something like this:
public enum TestEnum
{
[EnumString("Value One")]
Value1,
[EnumString("Value Two")]
Value2,
[EnumString("Value Three")]
Value3
}
EnumTest test = EnumTest.Value1;
Console.Write(test.ToStringEx());
The code to accomplish this is pretty simple:
[AttributeUsage(AttributeTargets.Field)]
public class EnumStringAttribute : Attribute
{
private string enumString;
public EnumStringAttribute(string EnumString)
{
enumString = EnumString;
}
public override string ToString()
{
return enumString;
}
}
public static class ExtensionMethods
{
public static string ToStringEx(this Enum enumeration)
{
Type type = enumeration.GetType();
FieldInfo field = type.GetField(enumeration.ToString());
var enumString =
(from attribute in field.GetCustomAttributes(true)
where attribute is EnumStringAttribute
select attribute).FirstOrDefault();
if (enumString != null)
return enumString.ToString();
// otherwise...
return enumeration.ToString();
}
}
[TestMethod()]
public void ToStringTest()
{
Assert.AreEqual("Value One", TestEnum.Value1.ToStringEx());
Assert.AreEqual("Value Two", TestEnum.Value2.ToStringEx());
Assert.AreEqual("Value Three", TestEnum.Value3.ToStringEx());
}
The credit goes to this post.
I have a handy Extension method for exactly this
public static class EnumExtensions
{
public static string ToNonPascalString(this Enum enu)
{
return Regex.Replace(enu.ToString(), "([A-Z])", " $1").Trim();
}
public T EnumFromString<T>(string value) where T : struct
{
string noSpace = value.Replace(" ", "");
if (Enum.GetNames(typeof(T)).Any(x => x.ToString().Equals(noSpace)))
{
return (T)Enum.Parse(typeof(T), noSpace);
}
return default(T);
}
}
Example:
public enum Test
{
UnitedStates,
NewZealand
}
// from enum to string
string result = Test.UnitedStates.ToNonPascalString(); // United States
// from string to enum
Test myEnum = EnumExtensions.EnumFromString<Test>("New Zealand"); // NewZealand
The following code will convert AbcDefGhi to Abc Def Ghi.
public static string ConvertEnum(this string value)
{
string result = string.Empty;
char[] letters = value.ToCharArray();
foreach (char c in letters)
if (c.ToString() != c.ToString().ToLower())
result += " " + c;
else
result += c.ToString();
return result;
}
Related
I have a string with the following structure:
Student Name________AgeAddress_______________________Bithday___Lvl
Example:
Jonh Smith 016Some place in NY, USA 01/01/2014L01
As you can see, there is no delimited character like | or ,
Also, there is no space between fields (if you check, there is no space between Age/Address and Birthday/Level.
The size of each field is static so if data's length is less then it will contains white spaces.
I have a class that need to be filled with that information:
public class StudentData
{
public char[] _name = new char[20];
public string name;
public char[] _age = new char[3];
public string age;
public char[] _address = new char[30];
public string address;
public char[] _bday = new char[10];
public string bday;
public char[] _level = new char[3];
public string level;
}
Is there any way to do this automatically and dynamically?
I mean I really don't want to code like this:
myClass.name = stringLine.substring(0,19);
myClass.age = stringLine.substring(20,22);
That's because I have way more fields that the ones added in this example & way more string lines with other different data.
Update: There were supposed to be a lot of spaces between "Smith" and "016", but I don't know how to edit it.
Update2: If I use StringReader.Read() I can evade to use substring and indexes, but it isn't still so dynamically because I would need to repeat those 3 lines for each field.
StringReader reader = new StringReader(stringLine);
reader.Read(myClass._name, 0 myClass._name.Length);
myClass.name = new string(myClass._name);
Given your requirement I came up with an interesting solution. All be-it it may be more complex and longer than using the String.SubString() method as stated.
However this solution is transferable to other types and other string. I used a concept of Attributes, Properties, and Reflection to parse a string by a Fixed Length and setting the class Properties.
Note I did change your StudentData class to follow a more conventional coding style. Following this handy guide on MSDN: http://msdn.microsoft.com/en-us/library/xzf533w0(v=vs.71).aspx
Here is the new StudentData class. Note it uses the properties as opposed to fields. (Not discussed here).
public class StudentData
{
string name;
string age;
string address;
string bday;
string level;
[FixedLengthDelimeter(0, 20)]
public string Name { get { return this.name; } set { this.name = value; } }
[FixedLengthDelimeter(1, 3)]
public string Age { get { return this.age; } set { this.age = value; } }
[FixedLengthDelimeter(2, 30)]
public string Address { get { return this.address; } set { this.address = value; } }
[FixedLengthDelimeter(3, 10)]
public string BDay { get { return this.bday; } set { this.bday = value; } }
[FixedLengthDelimeter(4, 3)]
public string Level { get { return this.level; } set { this.level = value; } }
}
Note on each of the properties there is an Attribute called FixedLengthDelimeter that takes two parameters.
OrderNumber
FixedLength
The OrderNumber parameter denotes the order in the string (not the position) but the order in which we process from the string. The second parameter denotes the Length of the string when parsing the string. Here is the full attribute class.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class FixedLengthDelimeterAttribute : Attribute
{
public FixedLengthDelimeterAttribute(int orderNumber, int fixedLength)
{
this.fixedLength = fixedLength;
this.orderNumber = orderNumber;
}
readonly int fixedLength;
readonly int orderNumber;
public int FixedLength { get { return this.fixedLength; } }
public int OrderNumber { get { return this.orderNumber; } }
}
Now the attribute is simple enough. Accepts the two paramters we discussed eariler in the constructor.
Finally there is another method to parse the string into the object type such as.
public static class FixedLengthFormatter
{
public static T ParseString<T>(string inputString)
{
Type tType = typeof(T);
var properties = tType.GetProperties(BindingFlags.Instance | BindingFlags.Public); //;.Where(x => x.GetCustomAttributes(typeof(FixedLengthDelimeterAttribute), false).Count() > 0);
T newT = (T)Activator.CreateInstance(tType);
Dictionary<PropertyInfo, FixedLengthDelimeterAttribute> dictionary = new Dictionary<PropertyInfo, FixedLengthDelimeterAttribute>();
foreach (var property in properties)
{
var atts = property.GetCustomAttributes(typeof(FixedLengthDelimeterAttribute), false);
if (atts.Length == 0)
continue;
dictionary[property] = atts[0] as FixedLengthDelimeterAttribute;
}
foreach (var kvp in dictionary.OrderBy(x => x.Value.OrderNumber))
{
int length = kvp.Value.FixedLength;
if (inputString.Length < length)
throw new Exception("error on attribute order number:" + kvp.Value.OrderNumber + " the string is too short.");
string piece = inputString.Substring(0, length);
inputString = inputString.Substring(length);
kvp.Key.SetValue(newT, piece.Trim(), null);
}
return newT;
}
}
The method above is what does the string parsing. It is a pretty basic utility that reads all the properties that have the FixedLengthDelimeter attribute applied a Dictionary. That dictionary is then enumerated (ordered by OrderNumber) and then calling the SubString() method twice on the input string.
The first substring is to parse the next Token while the second substring resets the inputString to start processing the next token.
Finally as it is parsing the string it is then applying the parsed string to the property of the class Type provided to the method.
Now this can be used simply like this:
string data1 = "Jonh Smith 016Some place in NY, USA 01/01/2014L01";
StudentData student = FixedLengthFormatter.ParseString<StudentData>(data1);
What this does:
Parses a string against property attributes in a fixed length format.
What this does not do:
It does convert the parsed strings to another type. Therefore all the properties must be a string. (this can be easily adapted by adding some type casting logic in).
It is not well tested. This is only tested against a few samples.
It is not by all means the only or best solution out there.
You could use FileHelpers library (NuGet).
Just define the structure of your input file with attributes:
[FixedLengthRecord]
public class StudentData
{
[FieldFixedLength(20)]
[FieldTrim(TrimMode.Right)]
public string name;
[FieldFixedLength(3)]
public string age;
[FieldFixedLength(30)]
[FieldTrim(TrimMode.Right)]
public string address;
[FieldFixedLength(10)]
public string bday;
[FieldFixedLength(3)]
public string level;
}
Then simply read the file using FileHelperEngine<T>:
var engine = new FileHelperEngine<StudentData>();
var students = engine.ReadFile(filename);
I have the following class People:
class People
{
public enum Gender
{
Man = 'w',
Woman = 'f'
}
public struct Person
{
public string First, Last;
public Gender Gender;
public int Age;
public float Height, Weight;
}
public struct Group
{
public int Members;
public string Name;
}
}
Now, we are located in the class Program:
class Program
{
static void Main( string[] args )
{
People.Person p = new People.Person();
p.First = "Victor";
p.Last = "Barbu";
p.Age = 14;
p.Height = 175;
p.Weight = 62;
p.Gender = People.Gender.Man;
Console.ReadLine();
}
}
I want to put a line like this:
Console.Write( x.toString() );
How can I customize the x.toString() method, so the result to be the following in the Console
Victor Barbu
14 years
Man
175 cm
62 kg
?
Thanks in advance!
Just overrive the ToString() method
public override string ToString()
{
// return the value you want here.
}
You want to override the ToString method in the Person class. See: http://msdn.microsoft.com/en-us/library/ms173154(v=vs.80).aspx
In your case
public class Person
{
// Snip
public override string ToString()
{
return this.First + " " + this.Last;
}
}
If you then do
Console.WriteLine(person.ToString());
The expected output would be the first and last name, you can obviously extend this to include your other fields and line breaks, etc.
Side note; what you are doing is really "pretty printing" I would suggest creating a static method "public static string PrettyPrintPerson(Person p)" or similar, to deal with textual formatting of the class.
class People
{
public override string ToString()
{
//This will return exactly what you just put down with line breaks
// then just call person.ToString()
return string.format("{1} {2}{0}{3} years{0}{4}{0}{5} cm{0}{6} kg", Environment.NewLine,
First, Last, Age, Gender, Height, Weight);
}
}
i need an enum or something similiar to do something like this:
public enum MyStringEnum {
[StringValue("Foo A")] Foo = "A",
[StringValue("Foo B")] Foo = "B" }
is this possible? my example, i return back a dataset with a value represented as either A,B,C,D,E .. i need a solution to return this as a string representation?
i guess the obvious would be to create an extension method or something which just had a switch statement in and return a string .. any other cleaner solutions?
regards,
dave
Here is something we use for our MVC applications to retrieve a display name for our enums. It uses a custom attribute and an extension method to retrieve the enum display name.
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class EnumDisplayNameAttribute : Attribute
{
public EnumDisplayNameAttribute(string displayName)
{
DisplayName = displayName;
}
public string DisplayName { get; set; }
}
public static string GetDisplayName(this Enum enumType)
{
var displayNameAttribute = enumType.GetType()
.GetField(enumType.ToString())
.GetCustomAttributes(typeof(EnumDisplayNameAttribute), false)
.FirstOrDefault() as EnumDisplayNameAttribute;
return displayNameAttribute != null ? displayNameAttribute.DisplayName : Enum.GetName(enumType.GetType(), enumType);
}
Usage on the enum:
public enum Foo
{
[EnumDisplayName("Foo Bar")]
Bar = 0
}
Getting back the display name:
var f = Foo.Bar;
var name = f.GetDisplayName();
Would it be an option not to use enum and use structs instead?
struct FooEnum
{
private int value;
private string name;
private FooEnum(int value, string name)
{
this.name = name;
this.value = value;
}
public static readonly FooEnum A = new FooEnum(0, "Foo A");
public static readonly FooEnum B = new FooEnum(1, "Foo B");
public static readonly FooEnum C = new FooEnum(2, "Foo C");
public static readonly FooEnum D = new FooEnum(3, "Foo D");
public override string ToString()
{
return this.name;
}
//TODO explicit conversion to int etc.
}
You could then use FooEnum like an enum with an own ToString() overload:
FooEnum foo = FooEnum.A;
string s = foo.ToString(); //"Foo A"
If you want to do something like this:
MyStringEnum value = MyStringEnum.A;
string description = value.GetDescription();
// description == "Foo A"
Setup your enum like this:
public enum MyStringEnum
{
[Description("Foo A")]
A,
[Description("Foo B")]
B
}
And use a utility/extension method that reads the attribute:
public static string GetDescription(this MyStringEnum enumerationValue)
{
Type type = enumerationValue.GetType();
string name = enumerationValue.ToString();
//Tries to find a DescriptionAttribute for a potential friendly name for the enum
MemberInfo[] member = type.GetMember(name);
if (member != null && member.Length > 0)
{
object[] attributes = member[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
{
//Pull out the description value
return ((DescriptionAttribute)attributes[0]).Description;
}
}
return name;
}
I've seen this done where I would put
MyStringEnum.Foo.ToString();
In this case it would give "A"
The cleanest solution for this problem is to create a custom attribute that will store the string value you want for the enum constant. I've used that strategy in the past and it worked out fairly well. Here's a blog post detailing the work involved:
Enum With String Values In C# - Stefan Sedich's Blog
Of course this is only necessary if you need some kind of meaningful text. If the name of the enum constant works for you...then you can simply call ToString().
I have a string property that I would like to be able to force two things with:
- It can only be set to specific vaues within a pre-defined list,
- Error checking of the property's value can be performed at compile time.
An enum fits the bill perfectly except that in my list of pre-defined strings there is one with a hyphen and enum values cannot contain hyphens. To illustrate the ideal solution if an enum could contain hyphens I would create an enum of:
public enum SIPEventPackagesEnum
{
dialog,
message-summary,
refer
}
To use:
SIPEventPackagesEnum EventPackage = SIPEventPackagesEnum.message-summary;
To set:
string eventPackageStr = "message-summary";
SIPEventPackagesEnum EventPackage = (SIPEventPackagesEnum)Enum.Parse(typeof(SIPEventPackagesEnum), eventPackageStr, true);
In the above cases it's impossible to set the EventPackage property to anything but one of the enum values and is intuitive to use since intellisense will list the available options.
Due to the inability to use a hyphen, and I cannot change the pre-defined list to remove the hyphen, the very crude approach is to use a struct and have a "Value" property on the struct that does the enforcing in its setter, see below. It's very verbose compared to using an enum and also doesn't allow any compile time checking and isn't very intuitive.
Has anyone encountered this problem before and have a better solution? I have multiple lists with items containing hyphens so it's not a once off.
public struct SIPEventPackages
{
public const string DIALOG = "dialog";
public const string MESSAGE_SUMMARY = "message-summary";
public const string REFER = "refer";
public string Value
{
get { return Value; }
set
{
if (IsValid(value))
{
Value = value.ToLower();
}
else
{
throw new ArgumentException(value + " is invalid for a SIP event package.");
}
}
}
public bool IsValid(string value)
{
if (value.IsNullOrBlank())
{
return false;
}
else if (value.ToLower() == DIALOG || value.ToLower() == MESSAGE_SUMMARY || value.ToLower() == REFER)
{
return true;
}
else
{
return false;
}
}
public override string ToString()
{
return Value;
}
}
Seems this is simple using an delegate
Func<string, bool> isValid = str =>
{
List<string> validLst = new List<string>() { "dialog","message-summary","refer" };
if (validLst.Find(x => string.Equals(x,str,StringComparison.InvariantCultureIgnoreCase)) == null)
return false;
return true;
};
var teststr1 = "message-summary";
var teststr2 = "wrongone";
isValid(teststr1);
isValid(teststr2);
Uptdate:
Otherwise you can use the enum approach in a little different way. Have a enum value without an hyphen. And just strip the hyphens from your source string when parse the enum values. this will work as you expected
public enum SIPEventPackagesEnum
{
dialog,
messagesummary,
refer
}
string eventPackageStr = "message-summary";
SIPEventPackagesEnum EventPackage = (SIPEventPackagesEnum)Enum.Parse(typeof(SIPEventPackagesEnum), eventPackageStr.Replace("-",""), true);
You could create the enum without -, then have a static Dictionary<SIPEventPackagesEnum
,string> in a helper class mapping from enum value to string to convert from enum to string, then use Enum.Parse(typeof(SIPEventPackagesEnum), str.Replace("-", "")) when converting from string to enum.
Or use _ instead of - and replace _ with - and vice versa when required
Or use camel case in the enum values and replace a capital letter within the enum name with "-<lowercase letter>" using a regex
I managed to fine tune my approach so that it's almost as good as an enum albeit with a lot more plumbing code required. It's worth the plumbing code to save potential misues problems in the future.
public struct SIPEventPackage
{
public static SIPEventPackage None = new SIPEventPackage(null);
public static SIPEventPackage Dialog = new SIPEventPackage("dialog");
public static SIPEventPackage MessageSummary = new SIPEventPackage("message-summary");
public static SIPEventPackage Refer = new SIPEventPackage("refer");
private string m_value;
private SIPEventPackage(string value)
{
m_value = value;
}
public override string ToString()
{
return m_value;
}
public static SIPEventPackage Parse(string value)
{
if (!IsValid(value))
{
throw new ArgumentException("The value is not valid for a SIPEventPackage.");
}
else
{
string trimmedValue = value.Trim().ToLower();
switch (trimmedValue)
{
case "dialog": return SIPEventPackage.Dialog;
case "message-summary": return SIPEventPackage.MessageSummary;
case "refer": return SIPEventPackage.Refer;
default: throw new ArgumentException("The value is not valid for a SIPEventPackage.");
}
}
}
}
There's a little bit more plumbing required, implementing an IsValid method and operator == and a few more, but the main thing is I can now use the struct in almost an identical way to an enum and can have items with hyphens.
This question already has answers here:
String representation of an Enum
(37 answers)
Localizing enum descriptions attributes
(8 answers)
Closed 9 years ago.
I have an enumeration like
Enum Complexity
{
NotSoComplex,
LittleComplex,
Complex,
VeryComplex
}
And I want to use it in a dropdown list, but don't want to see such Camel names in list (looks really odd for users). Instead I would like to have in normal wording, like
Not so complex
Little complex (etc)
Also, my application is multi-lang and I would like to be able to display those strings localized, and I use a helper, TranslationHelper(string strID) which gives me the localized version for a string id.
I have a working solution, but not very elegant:
I create a helper class for the enum, with one member Complexity and ToString() overwritten, like below (code simplified)
public class ComplexityHelper
{
public ComplexityHelper(Complexity c, string desc)
{ m_complex = c; m_desc=desc; }
public Complexity Complexity { get { ... } set {...} }
public override ToString() { return m_desc; }
//Then a static field like this
private static List<Complexity> m_cxList = null;
// and method that returns the status lists to bind to DataSource of lists
public static List<ComplexityHelper> GetComplexities()
{
if (m_cxList == null)
{
string[] list = TranslationHelper.GetTranslation("item_Complexities").Split(',');
Array listVal = Enum.GetValues(typeof(Complexities));
if (list.Length != listVal.Length)
throw new Exception("Invalid Complexities translations (item_Complexities)");
m_cxList = new List<Complexity>();
for (int i = 0; i < list.Length; i++)
{
Complexity cx = (ComplexitylistVal.GetValue(i);
ComplexityHelper ch = new ComplexityHelper(cx, list[i]);
m_cxList.Add(ch);
}
}
return m_cxList;
}
}
While workable, I'm not happy with it, since I have to code it similarily for various enums I need to use in picklists.
Does anyone have a suggestion for a simpler or more generic solution?
Thanks
Bogdan
Basic Friendly names
Use the Description attribute:*
enum MyEnum
{
[Description("This is black")]
Black,
[Description("This is white")]
White
}
And a handy extension method for enums:
public static string GetDescription(this Enum value)
{
FieldInfo field = value.GetType().GetField(value.ToString());
object[] attribs = field.GetCustomAttributes(typeof(DescriptionAttribute), true);
if(attribs.Length > 0)
{
return ((DescriptionAttribute)attribs[0]).Description;
}
return string.Empty;
}
Used like so:
MyEnum val = MyEnum.Black;
Console.WriteLine(val.GetDescription()); //writes "This is black"
(Note this doesn't exactly work for bit flags...)
For localization
There is a well-established pattern in .NET for handling multiple languages per string value - use a resource file, and expand the extension method to read from the resource file:
public static string GetDescription(this Enum value)
{
FieldInfo field = value.GetType().GetField(value.ToString());
object[] attribs = field.GetCustomAttributes(typeof(DescriptionAttribute), true));
if(attribs.Length > 0)
{
string message = ((DescriptionAttribute)attribs[0]).Description;
return resourceMgr.GetString(message, CultureInfo.CurrentCulture);
}
return string.Empty;
}
Any time we can leverage existing BCL functionality to achieve what we want, that's definitely the first route to explore. This minimizes complexity and uses patterns already familiar to many other developers.
Putting it all together
To get this to bind to a DropDownList, we probably want to track the real enum values in our control and limit the translated, friendly name to visual sugar. We can do so by using an anonymous type and the DataField properties on the list:
<asp:DropDownList ID="myDDL"
DataTextField="Description"
DataValueField="Value" />
myDDL.DataSource = Enum.GetValues(typeof(MyEnum)).OfType<MyEnum>().Select(
val => new { Description = val.GetDescription(), Value = val.ToString() });
myDDL.DataBind();
Let's break down that DataSource line:
First we call Enum.GetValues(typeof(MyEnum)), which gets us a loosely-typed Array of the values
Next we call OfType<MyEnum>() which converts the array to an IEnumerable<MyEnum>
Then we call Select() and provide a lambda that projects a new object with two fields, Description and Value.
The DataTextField and DataValueField properties are evaluated reflectively at databind-time, so they will search for fields on DataItem with matching names.
-Note in the main article, the author wrote their own DescriptionAttribute class which is unnecessary, as one already exists in .NET's standard libraries.-
The use of attributes as in the other answers is a good way to go, but if you just want to use the text from the values of the enum, the following code will split based on the camel-casing of the value:
public static string GetDescriptionOf(Enum enumType)
{
Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled);
return capitalLetterMatch.Replace(enumType.ToString(), " $&");
}
Calling GetDescriptionOf(Complexity.NotSoComplex) will return Not So Complex. This can be used with any enum value.
To make it more useful, you could make it an extension method:
public static string ToFriendlyString(this Enum enumType)
{
Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled);
return capitalLetterMatch.Replace(enumType.ToString(), " $&");
}
You cal now call it using Complexity.NotSoComplex.ToFriendlyString() to return Not So Complex.
EDIT: just noticed that in your question you mention that you need to localise the text. In that case, I'd use an attribute to contain a key to look up the localised value, but default to the friendly string method as a last resort if the localised text cannot be found. You would define you enums like this:
enum Complexity
{
[LocalisedEnum("Complexity.NotSoComplex")]
NotSoComplex,
[LocalisedEnum("Complexity.LittleComplex")]
LittleComplex,
[LocalisedEnum("Complexity.Complex")]
Complex,
[LocalisedEnum("Complexity.VeryComplex")]
VeryComplex
}
You would also need this code:
[AttributeUsage(AttributeTargets.Field, AllowMultiple=false, Inherited=true)]
public class LocalisedEnum : Attribute
{
public string LocalisationKey{get;set;}
public LocalisedEnum(string localisationKey)
{
LocalisationKey = localisationKey;
}
}
public static class LocalisedEnumExtensions
{
public static string ToLocalisedString(this Enum enumType)
{
// default value is the ToString();
string description = enumType.ToString();
try
{
bool done = false;
MemberInfo[] memberInfo = enumType.GetType().GetMember(enumType.ToString());
if (memberInfo != null && memberInfo.Length > 0)
{
object[] attributes = memberInfo[0].GetCustomAttributes(typeof(LocalisedEnum), false);
if (attributes != null && attributes.Length > 0)
{
LocalisedEnum descriptionAttribute = attributes[0] as LocalisedEnum;
if (description != null && descriptionAttribute != null)
{
string desc = TranslationHelper.GetTranslation(descriptionAttribute.LocalisationKey);
if (desc != null)
{
description = desc;
done = true;
}
}
}
}
if (!done)
{
Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled);
description = capitalLetterMatch.Replace(enumType.ToString(), " $&");
}
}
catch
{
description = enumType.ToString();
}
return description;
}
}
To get the localised descriptions, you would then call:
Complexity.NotSoComplex.ToLocalisedString()
This has several fallback cases:
if the enum has a LocalisedEnum attribute defined, it will use the key to look up the translated text
if the enum has a LocalisedEnum attribute defined but no localised text is found, it defaults to using the camel-case split method
if the enum does not have a LocalisedEnum attribute defined, it will use the camel-case split method
upon any error, it defaults to the ToString of the enum value
I use the following class
public class EnumUtils
{
/// <summary>
/// Reads and returns the value of the Description Attribute of an enumeration value.
/// </summary>
/// <param name="value">The enumeration value whose Description attribute you wish to have returned.</param>
/// <returns>The string value portion of the Description attribute.</returns>
public static string StringValueOf(Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
{
return attributes[0].Description;
}
else
{
return value.ToString();
}
}
/// <summary>
/// Returns the Enumeration value that has a given Description attribute.
/// </summary>
/// <param name="value">The Description attribute value.</param>
/// <param name="enumType">The type of enumeration in which to search.</param>
/// <returns>The enumeration value that matches the Description value provided.</returns>
/// <exception cref="ArgumentException">Thrown when the specified Description value is not found with in the provided Enumeration Type.</exception>
public static object EnumValueOf(string value, Type enumType)
{
string[] names = Enum.GetNames(enumType);
foreach (string name in names)
{
if (StringValueOf((Enum)Enum.Parse(enumType, name)).Equals(value))
{
return Enum.Parse(enumType, name);
}
}
throw new ArgumentException("The string is not a description or value of the specified enum.");
}
Which reads an attribute called description
public enum PuppyType
{
[Description("Cute Puppy")]
CutePuppy = 0,
[Description("Silly Puppy")]
SillyPuppy
}
Thank you all for all answers.
Finally I used a combination from Rex M and adrianbanks, and added my own improvements, to simplify the binding to ComboBox.
The changes were needed because, while working on the code, I realized sometimes I need to be able to exclude one enumeration item from the combo.
E.g.
Enum Complexity
{
// this will be used in filters,
// but not in module where I have to assign Complexity to a field
AllComplexities,
NotSoComplex,
LittleComplex,
Complex,
VeryComplex
}
So sometimes I want the picklist to show all but AllComplexities (in add - edit modules) and other time to show all (in filters)
Here's what I did:
I created a extension method, that uses Description Attribute as localization lookup key. If Description attribute is missing, I create the lookup localization key as EnumName_
EnumValue. Finally, if translation is missing I just split enum name based on camelcase to separate words as shown by adrianbanks. BTW, TranslationHelper is a wrapper around resourceMgr.GetString(...)
The full code is shown below
public static string GetDescription(this System.Enum value)
{
string enumID = string.Empty;
string enumDesc = string.Empty;
try
{
// try to lookup Description attribute
FieldInfo field = value.GetType().GetField(value.ToString());
object[] attribs = field.GetCustomAttributes(typeof(DescriptionAttribute), true);
if (attribs.Length > 0)
{
enumID = ((DescriptionAttribute)attribs[0]).Description;
enumDesc = TranslationHelper.GetTranslation(enumID);
}
if (string.IsNullOrEmpty(enumID) || TranslationHelper.IsTranslationMissing(enumDesc))
{
// try to lookup translation from EnumName_EnumValue
string[] enumName = value.GetType().ToString().Split('.');
enumID = string.Format("{0}_{1}", enumName[enumName.Length - 1], value.ToString());
enumDesc = TranslationHelper.GetTranslation(enumID);
if (TranslationHelper.IsTranslationMissing(enumDesc))
enumDesc = string.Empty;
}
// try to format CamelCase to proper names
if (string.IsNullOrEmpty(enumDesc))
{
Regex capitalLetterMatch = new Regex("\\B[A-Z]", RegexOptions.Compiled);
enumDesc = capitalLetterMatch.Replace(value.ToString(), " $&");
}
}
catch (Exception)
{
// if any error, fallback to string value
enumDesc = value.ToString();
}
return enumDesc;
}
I created a generic helper class based on Enum, which allow to bind the enum easily to DataSource
public class LocalizableEnum
{
/// <summary>
/// Column names exposed by LocalizableEnum
/// </summary>
public class ColumnNames
{
public const string ID = "EnumValue";
public const string EntityValue = "EnumDescription";
}
}
public class LocalizableEnum<T>
{
private T m_ItemVal;
private string m_ItemDesc;
public LocalizableEnum(T id)
{
System.Enum idEnum = id as System.Enum;
if (idEnum == null)
throw new ArgumentException(string.Format("Type {0} is not enum", id.ToString()));
else
{
m_ItemVal = id;
m_ItemDesc = idEnum.GetDescription();
}
}
public override string ToString()
{
return m_ItemDesc;
}
public T EnumValue
{
get { return m_ID; }
}
public string EnumDescription
{
get { return ToString(); }
}
}
Then I created a generic static method that returns a List>, as below
public static List<LocalizableEnum<T>> GetEnumList<T>(object excludeMember)
{
List<LocalizableEnum<T>> list =null;
Array listVal = System.Enum.GetValues(typeof(T));
if (listVal.Length>0)
{
string excludedValStr = string.Empty;
if (excludeMember != null)
excludedValStr = ((T)excludeMember).ToString();
list = new List<LocalizableEnum<T>>();
for (int i = 0; i < listVal.Length; i++)
{
T currentVal = (T)listVal.GetValue(i);
if (excludedValStr != currentVal.ToString())
{
System.Enum enumVal = currentVal as System.Enum;
LocalizableEnum<T> enumMember = new LocalizableEnum<T>(currentVal);
list.Add(enumMember);
}
}
}
return list;
}
and a wrapper to return list with all members
public static List<LocalizableEnum<T>> GetEnumList<T>()
{
return GetEnumList<T>(null);
}
Now let's put all things together and bind to actual combo:
// in module where we want to show items with all complexities
// or just filter on one complexity
comboComplexity.DisplayMember = LocalizableEnum.ColumnNames.EnumValue;
comboComplexity.ValueMember = LocalizableEnum.ColumnNames.EnumDescription;
comboComplexity.DataSource = EnumHelper.GetEnumList<Complexity>();
comboComplexity.SelectedValue = Complexity.AllComplexities;
// ....
// and here in edit module where we don't want to see "All Complexities"
comboComplexity.DisplayMember = LocalizableEnum.ColumnNames.EnumValue;
comboComplexity.ValueMember = LocalizableEnum.ColumnNames.EnumDescription;
comboComplexity.DataSource = EnumHelper.GetEnumList<Complexity>(Complexity.AllComplexities);
comboComplexity.SelectedValue = Complexity.VeryComplex; // set default value
To read selected the value and use it, I use code as below
Complexity selComplexity = (Complexity)comboComplexity.SelectedValue;