How to fetch an element's Display attribute in c# enums - c#

I have below enum and I want to fetch its element's Display attribute based on values assigned to it.
I need to create a function where I need to pass a value(1 for Economic) and it will return me related element's Display attribute.
public enum ProbabilityNames
{
[Display(Name = "Economic Probability")]
Economic = 1,
[Display(Name = "Reliability Probability")]
Reliability = 2
}

You can use reflection for that:
public static class ProbabilityNamesExtensions
{
public static DisplayAttribute GetDisplay(this ProbabilityNames value) =>
typeof(ProbabilityNames)
.GetField(Enum.GetName(typeof(ProbabilityNames), value))
.GetCustomAttributes(false)
.SingleOrDefault(attr => attr is DisplayAttribute) as DisplayAttribute;
public static string GetDisplayName(this ProbabilityNames value) =>
value.GetDisplay()?.Name;
}
You can use it like this:
ProbabilityNames.Economic.GetDisplay();
Or if you need to get the display based on an int value, you can just cast it:
((ProbabilityNames)1).GetDisplay();

Related

Dynamic assigned Properties for CsvHelper.Configuration.ClassMap<T>

I want to use CsvHelper.Configuration.ClassMap by dynamically assigned properties.
Usually you map a Property like this in a static manner: You have to assign each property and its 'text to display'.
using CsvHelper.Configuration;
public sealed class CleanSQLRowDescriptorMap : ClassMap<CleanSQLRowDescriptor>
{
public CleanSQLRowDescriptorMap()
{
Map(f => f.OriginalIndex).Name("Original Index");
Map(f => f.OriginalRow).Name("Original Row");
}
}
I want to do the following:
using CsvHelper.Configuration;
public sealed class CleanSQLRowDescriptorMap : ClassMap<CleanSQLRowDescriptor>
{
public CleanSQLRowDescriptorMap()
{
// Filter by attribute (implementation returns PropertyInfo List)
List<PropertyInfo> mappedProperties = CleanSQLRowDescriptor.Create().FilterPropertiesByAttribute();
// Dynamically assign each property and its assigned 'attribute value'
// At the moment I mapped the PropertyInfo.Name, but I actually need to use the Property as the static example above.
// Also need to figure out how to get the Attribute value (DisplayName in this example).
mappedProperties.ForEach(prop => Map(f => prop.Name).Name(prop.Name));
}
}
I currently have the following method used above:
[DisplayName("Original Index")]
public int OriginalIndex { get; set; }
[DisplayName("Original Row")]
public string OriginalRow { get; set; }
public string DonotWantToAssignThis { get; set; }
public List<PropertyInfo> FilterPropertiesByAttribute()
{
// This function already returns only the attributes that use
// [DisplayName] and other attributes defined for other properties,
// ignoring other properties that do not have any of these attributes.
return properties;
}
How can I use the PropertyInfo List of items to dynamically assign the ClassMap? I want to create a base class with these attributes as filters and all the classes implementing this base class would have the same capability, making it easier to 'maintain the mappings'.
I managed to figure it out, VS Code did not give me all the overloads for Map() function, so I missed overloads.
This one is used in all examples:
MemberMap<TClass, TMember> Map<TMember>(Expression<Func<TClass, TMember>> expression, bool useExistingMap = true);
I found this inside JoshClose/CSVHelper:
public MemberMap Map(Type classType, MemberInfo member, bool useExistingMap = true)
So instead of using 'Expression that requires the property name as TMember' which does not take the type I can now assign the MemberInfo directly.
The code below just shows a solution for a single attribute [DisplayName] by using its .DisplayName property value.
For additional Attributes like I have at the moment, I will need to handle the property value differently:
mappedProperties.ForEach(prop =>
{
Map(typeof(CleanSQLRowDescriptor), prop).Name(prop.GetCustomAttribute<DisplayNameAttribute>().DisplayName);
});

How to put spaces on a bound enum title on picker

I am attaching enum to a picker and onSelect i am binding to the actual value of the enum, not its title.
My enum is as follows:
public enum Reason
{
AnnualLeave = 12,
Emergency = 23,
MaternityLeave = 34
}
My class uses the following to bind the enum title to the picker
public Reason ReasonSelectedOption { get; set; }
public ObservableCollection<Reason> ReasonDisplay
{
get => new ObservableCollection<Reason>(Enum.GetValues(typeof(Reason)).OfType<Reason>().ToList());
}
The actual picker
<Picker
ItemsSource="{Binding ReasonDisplay}"
SelectedItem="{Binding ReasonSelectedOption}"
Title="Please Select"
HorizontalOptions="FillAndExpand" />
Everything works fine except in the actual picker, the options appear as AnnualLeave and MaternityLeave which is what's expected from my code but i want them to appear as Annual Leave and Maternity Leave (with the space inbetween) preserving the selecteditem value
Current case: When user selects AnnualLeave, selectedItem value is 12, if i convert to string the selected value becomes 0.
I am simply asking how to put spaces inbetween the enum options and also preserve the SelectedItem integer value
Here you have to keep in mind the internationalisation.
Even if you don't have localised texts now, you may have to support it in the future. So, keeping that in mind, you won't need simply to "split" the string, but to take a specific text from somewhere (i.e. translate it according to the culture).
You can achieve it with the help of some attributes, extension methods and some clever binding.
Let's say that you want to have a picker with 2 options - what is the property type. The PropertyType is an enum, that looks like this:
public enum PropertyType
{
House,
Apartment
}
Since the built-in Description attribute can't translate texts for us, we can use a custom attribute to assign a specific text to an enum type, like this:
public enum PropertyType
{
[LocalizedDescription(nameof(R.SingleFamilyHouse))]
House,
[LocalizedDescription(nameof(R.ApartmentBuilding))]
Apartment
}
The attribute code looks like this:
public class LocalizedDescriptionAttribute : DescriptionAttribute
{
private readonly ResourceManager resourceManager;
private readonly string resourceKey;
public LocalizedDescriptionAttribute(string resourceKey, Type resourceType = null)
{
this.resourceKey = resourceKey;
if (resourceType == null)
{
resourceType = typeof(R);
}
resourceManager = new ResourceManager(resourceType);
}
public override string Description
{
get
{
string description = resourceManager.GetString(resourceKey);
return string.IsNullOrWhiteSpace(description) ? $"[[{resourceKey}]]" : description;
}
}
}
R is my resx file. I have created a Resources folder and inside it I have 2 resx files - R.resx (for English strings) & R.de.resx (for German translation). If you don't want to have internationalisation now, you can change the implementation to get your strings from another place. But it is considered a good practice to always use a resx file, even if you only have 1 language. You never now what tomorrow may bring.
Here is my structure:
The idea behind LocalizedDescriptionAttribute class is that the built-in Description attribute isn't very useful for our case. So we'll have to take the resource key that we have provided it, translates and to override the Description attribute, which later we will reference.
Now we need to obtain the localised description text with this helper method:
public static class EnumExtensions
{
public static string GetLocalizedDescriptionFromEnumValue(this Enum value)
{
return !(value.GetType()
.GetField(value.ToString())
.GetCustomAttributes(typeof(LocalizedDescriptionAttribute), false)
.SingleOrDefault() is LocalizedDescriptionAttribute attribute) ? value.ToString() : attribute.Description;
}
}
Now, when we create the bindings for the Picker, we won't just use a simple Enum, but a specific PropertyTypeViewModel, which will have 2 properties - the Enum itself and a Name that will be displayed.
public class PropertyTypeViewModel : BaseViewModel
{
private string name;
public string Name
{
get => name;
set => SetValue(ref name, value);
}
private PropertyType type;
public PropertyType Type
{
get => type;
set => SetValue(ref type, value);
}
public PropertyTypeViewModel()
{
}
public PropertyTypeViewModel(PropertyType type)
: this()
{
Type = type;
Name = type.GetLocalizedDescriptionFromEnumValue();
}
}
The important line is the last one - Name = type.GetLocalizedDescriptionFromEnumValue();
The final thing that is left is to set your Picker's ItemsSource to your collection of PropertyTypeViewModels and ItemDisplayBinding to be pointing to the Name property - ItemDisplayBinding="{Binding Name}"
That's it - now you have a Picker with dynamic localised strings.
You could use a converter
public class EnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return null;
var valueAsString = value.ToString();
valueAsString = valueAsString.SplitCamelCase();
return valueAsString;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And for the SplitCamelCase I wrote this but I'm sure there are cleaner options:
public static string SplitCamelCase(this string str)
{
string result = "";
for (int i = 0; i < str.Count(); i++)
{
var letter = str[i];
var previousLetter = i != 0 ? str[i - 1] : 'A';
if (i != 0 && char.IsUpper(previousLetter) == false && char.IsUpper(letter)) result = result + " " + letter;
else result = result + letter;
}
return result;
}
Then just used it like so:
<TextBlock Text="{Binding Converter={StaticResource EnumToStringConverter}}"/>

Enforcing static class constants as a data type

I have a static class with constants. I am looking for options to create a method which takes a dictionary as an argument and enforcing the key to be one of the constants from the static class.Here is my static class with constants.
Here is what I am trying to do
And here is what I am trying to enforce
From the sound of it, an Enum would be more suited to what you're trying to do.
public enum MyConstants
{
FirstName,
LastName,
Title
}
public void CreateMe(Dictionary<MyConstants, string> propertyBag)
{
...
}
UPDATED
You could combine this with attributes to associate each enum with a specific string like so:
public enum PropertyNames
{
[Description("first_name")]
FirstName,
[Description("last_name")]
LastName,
[Description("title")]
Title
}
The value of each description attribute associated with each enum value could easily be grabbed via an extension method, like so:
public static class EnumExtensions
{
public static string GetDescription(this Enum value)
{
FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fieldInfo.GetCustomAttributes(
typeof(DescriptionAttribute),
false);
if (attributes != null &&
attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
}
Then in your "CreateMe"-method you can get the description and value of each dictionary entry by doing something similar to this:
void CreateMe(Dictionary<PropertyNames, string> propertyBag)
{
foreach (var propertyPair in propertyBag)
{
string propertyName = propertyPair.Key.GetDescription();
string propertyValue = propertyPair.Value;
}
}
Even though this has been already answered, there is another approach, like so:
public class MyOwnEnum
{
public string Value { get; private set; }
private MyOwnEnum(string value)
{
Value = value;
}
public static readonly MyOwnEnum FirstName = new MyOwnEnum("Firstname");
public static readonly MyOwnEnum LastName = new MyOwnEnum("LastName");
}
It behaves same way like Enum and can be used in your code with same syntax. I cannot give credit to whoever came up with it, but I believe I came upon it when searching for Enums with multiple values.
With strings you can't enforce fact that keys come from limited set of vialues compile time.
Use enum or custom class (possibly with implicit conversion to string) instead.

Parse StringValueAttribute to return Enum

I currently have a windows phone 8.1 runtime project with enums that use a string value attribute. I want to be able to get an enum value by using the string value attribute, for example use "world" to get the enum value of summer. I am using Windows phone 8.1 runtime so most methods that I have found do not work.
Thanks in advance.
public enum test
{
[StringValue("hello")]
school,
[StringValue("world")]
summer,
[StringValue("fall")]
car
}
public class StringValueAttribute : Attribute
{
private string _value;
public StringValueAttribute(string value)
{
_value = value;
}
public string Value
{
get { return _value; }
}
}
To get to your Attributes you will need to use a method/extension. Folowing this question and answer you can make such a thing:
public class StringValueAttribute : Attribute
{
private string _value;
public StringValueAttribute(string value)
{
_value = value;
}
public string Value
{
get { return _value; }
}
public static string GetStringValue(Enum value)
{
Type type = value.GetType();
FieldInfo fi = type.GetRuntimeField(value.ToString());
return (fi.GetCustomAttributes(typeof(StringValueAttribute), false).FirstOrDefault() as StringValueAttribute).Value;
}
}
Then using this line of code:
string stringTest = StringValueAttribute.GetStringValue(test.summer);
will give a result of "world". (Opposite what you wanted, but hopefuly will give you an idea how to deal with the problem).
Depending on what you want to achieve, you can probably use different methods linke: using Dictionary, struct, properties and probably different ways.
As for parsing Enum values you can achieve it like this:
test testValue = test.summer;
string testString = testValue.ToString();
test EnumValue = (test)Enum.Parse(typeof(test), testString);
EDIT
If you want to get enum from attribute, then this method (probably should be improved) should do the job:
public static T GetFromAttribute<T>(string attributeName)
{
Type type = typeof(T);
return (T)Enum.Parse(typeof(T), type.GetRuntimeFields().FirstOrDefault(
x => (x.CustomAttributes.Count() > 0 && (x.CustomAttributes.FirstOrDefault().ConstructorArguments.FirstOrDefault().Value as string).Equals(attributeName))).Name);
}
Usage:
test EnumTest = StringValueAttribute.GetFromAttribute<test>("world");

Fill struct with String[]?

I'm parsing a CSV file and placing the data in a struct. I'm using the TextFieldParser from this question and it's working like a charm except that it returns a String[]. Currently I have the ugly process of:
String[] row = parser.ReadFields();
DispatchCall call = new DispatchCall();
if (!int.TryParse(row[0], out call.AccountID)) {
Console.WriteLine("Invalid Row: " + parser.LineNumber);
continue;
}
call.WorkOrder = row[1];
call.Description = row[2];
call.Date = row[3];
call.RequestedDate = row[4];
call.EstStartDate = row[5];
call.CustomerID = row[6];
call.CustomerName = row[7];
call.Caller = row[8];
call.EquipmentID = row[9];
call.Item = row[10];
call.TerritoryDesc = row[11];
call.Technician = row[12];
call.BillCode = row[13];
call.CallType = row[14];
call.Priority = row[15];
call.Status = row[16];
call.Comment = row[17];
call.Street = row[18];
call.City = row[19];
call.State = row[20];
call.Zip = row[21];
call.EquipRemarks = row[22];
call.Contact = row[23];
call.ContactPhone = row[24];
call.Lat = row[25];
call.Lon = row[26];
call.FlagColor = row[27];
call.TextColor = row[28];
call.MarkerName = row[29];
The struct consists of all those fields being Strings except for AccountID being an int. It annoys me that they're not strongly typed, but let's over look that for now. Given that parser.ReadFields() returns a String[] is there a more efficient way to fill a struct (possibly converting some values such as row[0] needing to become an int) with the values in the array?
**EDIT:**One restriction I forgot to mention that may impact what kind of solutions will work is that this struct is [Serializable] and will be sent Tcp somewhere else.
Your mileage may vary on whether it is a better solution, but you could use reflection and define an Attribute class that you use to mark your struct members with. The attribute would take the array index as an argument. Assigning the value from the right array element would then happen by using reflection.
You could define your attribute like this:
[AttributeUsage(AttributeTargets.Property)]
public sealed class ArrayStructFieldAttribute : Attribute
{
public ArrayStructFieldAttribute(int index)
{
this.index = index;
}
private readonly int index;
public int Index {
get {
return index;
}
}
}
This means the attribute can simply be used to associate an int value named Index with a property.
Then, you could mark your properties in the struct with that attribute (just some exemplary lines):
[ArrayStructField(1)]
public string WorkOrder { // ...
[ArrayStructField(19)]
public string City { // ...
The values could then be set with the Type object for your struct type (you can obtain it with the typeof operator):
foreach (PropertyInfo prop in structType.GetProperties()) {
ArrayStructFieldAttribute attr = prop.GetCustomAttributes(typeof(ArrayStructFieldAttribute), false).Cast<ArrayStructFieldAttribute>().FirstOrDefault();
if (attr != null) {
// we have found a property that you want to load from an array element!
if (prop.PropertyType == typeof(string)) {
// the property is a string property, no conversion required
prop.SetValue(boxedStruct, row[attr.Index]);
} else if (prop.PropertyType == typeof(int)) {
// the property is an int property, conversion required
int value;
if (!int.TryParse(row[attr.Index], out value)) {
Console.WriteLine("Invalid Row: " + parser.LineNumber);
} else {
prop.SetValue(boxedStruct, value);
}
}
}
}
This code iterates over all properties of your struct type. For each property, it checks for our custom attribute type defined above. If such an attribute is present, and if the property type is string or int, the value is copied from the respective array index.
I am checking for string and int properties as that's the two data types you mentioned in your question. even though you have only one particular index that contains an int value now, it's good for maintainability if this code is prepared to handle any index as a string or an int property.
Note that for a greater number of types to handle, I'd suggest not using a chain of if and else if, but rather a Dictionary<Type, Func<string, object>> that maps property types to conversion functions.
If you want to create something very flexible you can mark each property on DispatchCall using a custom attribute. Something like this:
class DispatchCall {
[CsvColumn(0)]
public Int32 AccountId { get; set; }
[CsvColumn(1)]
public String WorkOrder { get; set; }
[CsvColumn(3, Format = "yyyy-MM-dd")]
public DateTime Date { get; set; }
}
This allows you to associate each property with a column. For each row you can then iterate over all properties and by using the attribute you can assign the right value to the right property. You will have to do some type conversion from string to numbers, dates and perhaps enums. You can add extra properties to the attribute to assist you in that process. In the example I invented Format which should be used when a DateTime is parsed:
Object ParseValue(String value, TargetType targetType, String format) {
if (targetType == typeof(String))
return value;
if (targetType == typeof(Int32))
return Int32.Parse(value);
if (targetType == typeof(DateTime))
DateTime.ParseExact(value, format, CultureInfo.InvariantCulture);
...
}
Using TryParse methods in the above code can improve the error handling by allowing you to provide more context when an unparsable value is encountered.
Unfortunately, this approach is not very efficient because the reflection code will be executed for each row in your input file. If you want to make this more efficient you need to dynamically create a compiled method by reflecting once over DispatchCall that you then can apply on each row. It is possible but not particular easy.
How dependent are you on the library that you're using? I've found File Helpers to be quite useful for this sort of thing. Your code would look something like:
using FileHelpers;
// ...
[DelimitedRecord(",")]
class DispatchCall {
// Just make sure these are in order
public int AccountID { get; set; }
public string WorkOrder { get; set; }
public string Description { get; set; }
// ...
}
// And then to call the code
var engine = new FileHelperEngine(typeof(DispatchCall));
engine.Options.IgnoreFirstLines = 1; // If you have a header row
DispatchCall[] data = engine.ReadFile(FileName) as DispatchCall[];
You now have a DispatchCall array, and the engine did all the heavy lifting for you.
Use reflection as #Grozz suggested in the comment. Mark each property of the struct class with an attribute (ie [ColumnOrdinal] ) and then use this to map the information with the proper column. If you have double, decimal and so on as a target, you should also consider using Convert.ChangeType to proper convert in the target type. if you are not happy with the performances, you can enjoy create a DynamicMethod on the fly, more challenging, but really performant and beautiful. The challenge is to write the IL instruction in memory to do the "plumbing" you did by hand ( I usually create some example code, and then look inside it with IL spy as a starting point ). of course you will cache somewhere such dynamic methods so creating them is requested just once.
The first thing that comes to mind is to use reflection to iterate over the properties and match them up to the elements in the string[] based on an attribute value.
public struct DispatchCall
{
[MyAttribute(CsvIndex = 1)]
public string WorkOrder { get; set; }
}
MyAttribute would just be a custom attribute with an index that would match up to the field position in the CSV.
var row = parser.ReadFields();
for each property that has MyAttribute...
var indexAttrib = MyAttribute attached to property
property.Value = row[indexAttrib.Index]
next
(Pseudocode, obviously)
or
[StructLayout(LayoutKind.Sequential)] // keep fields in order
public strict DispatchCall
{
public string WorkOrder;
public string Description;
}
StructLayout will keep the struct fields in order, so you can iterate over them without having to explicitly specify a column number for each field. That can save some maintenance if you have a lot of fields.
Or, you could skip the process entirely, and store the field names in a dictionary:
var index = new Dictionary<int, string>();
/// populate index with row index : field name values, preferable from some sort of config file or database
index[0] = "WorkOrder";
index[1] = "Description";
...
var values = new Dictionary<string,object>();
for(var i=0;i<row.Length;i++)
{
values.Add(index[i],row[i]);
}
That's easier to load, but doesn't really take advantage of strong typing, which makes this less than ideal.
You could also generate a dynamic method or a T4 template. You could generate code from a mapping file in the format
0,WorkOrder
1,Description
...
load that, and generate a method that looks like this:
/// emit this
call.WorkOrder = row[0];
call.Description = row[1];
etc.
That approach is used in a few micro-ORMs floating around and seems to work pretty well.
Ideally, your CSV would include a row with field names that would make this a lot easier.
OR, yet another approach, use StructLayout along with a dynamic method to avoid having to keep a field:column_index mapping aside from the struct itself.
OR, create an enum
public enum FieldIndex
{
WorkOrder=0
,
Description // only have to specify explicit value for the first item in the enum
, /// ....
,
MAX /// useful for getting the maximum enum integer value
}
for(var i=0;i<FieldIndex.MAX;i++)
{
var fieldName = ((FieldIndex)i).ToString(); /// get string enum name
var value = row[i];
// use reflection to find the property/field FIELDNAME, and set it's value to VALUE.
}
if you are going for speed you could a brittle switch statement.
var columns = parser.ReadFields();
for (var i = 0; i < columns.Length; i++)
{
SetValue(call, i, columns[i]);
}
private static void SetValue(DispatchCall call, int column, string value)
{
switch column
{
case 0:
SetValue(ref call.AccountId, (value) => int.Parse, value);
return;
case 1:
SetValue(ref call.WorkOrder, (value) => value, value);
return;
...
default:
throw new UnexpectedColumnException();
}
}
private static void SetValue<T>(
ref T property,
Func<string, T> setter
value string)
{
property = setter(value);
}
Its a shame that TextFieldParser does not allow you to read one field at a time, then you could avoid building and indexing the columns array.

Categories