Using EnumMemberAttribute and doing automatic string conversions - c#

I have the following code
[DataContract]
public enum StatusType
{
[EnumMember(Value = "A")]
All,
[EnumMember(Value = "I")]
InProcess,
[EnumMember(Value = "C")]
Complete,
}
I'd like to do the following:
var s = "C";
StatusType status = SerializerHelper.ToEnum<StatusType>(s); //status is now StatusType.Complete
string newString = SerializerHelper.ToEnumString<StatusType>(status); //newString is now "C"
I've done the second part using DataContractSerializer (see code below), but it seems like a lot of work.
Am I missing something obvious? Ideas? Thanks.
public static string ToEnumString<T>(T type)
{
string s;
using (var ms = new MemoryStream())
{
var ser = new DataContractSerializer(typeof(T));
ser.WriteObject(ms, type);
ms.Position = 0;
var sr = new StreamReader(ms);
s = sr.ReadToEnd();
}
using (var xml = new XmlTextReader(s, XmlNodeType.Element, null))
{
xml.MoveToContent();
xml.Read();
return xml.Value;
}
}

Here is my proposition - it should give you the idea on how to do this (check also Getting attributes of Enum's value):
public static string ToEnumString<T>(T type)
{
var enumType = typeof (T);
var name = Enum.GetName(enumType, type);
var enumMemberAttribute = ((EnumMemberAttribute[])enumType.GetField(name).GetCustomAttributes(typeof(EnumMemberAttribute), true)).Single();
return enumMemberAttribute.Value;
}
public static T ToEnum<T>(string str)
{
var enumType = typeof(T);
foreach (var name in Enum.GetNames(enumType))
{
var enumMemberAttribute = ((EnumMemberAttribute[])enumType.GetField(name).GetCustomAttributes(typeof(EnumMemberAttribute), true)).Single();
if (enumMemberAttribute.Value == str) return (T)Enum.Parse(enumType, name);
}
//throw exception or whatever handling you want or
return default(T);
}

If your project references Newtonsoft.Json (what doesn't these days?!), then there is a simple one line solution that doesn't need reflection:
public static string ToEnumString<T>(T value)
{
return JsonConvert.SerializeObject(value).Replace("\"", "");
}
public static T ToEnum<T>(string value)
{
return JsonConvert.DeserializeObject<T>($"\"{value}\"");
}
The ToEnumString method will only work if you have the StringEnumConverter registered in your JsonSerializerSettings (see JavaScriptSerializer - JSON serialization of enum as string), e.g.
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = { new StringEnumConverter() }
};
Another advantage of this method is that if only some of your enum elements have the member attribute, things still work as expected, e.g.
public enum CarEnum
{
Ford,
Volkswagen,
[EnumMember(Value = "Aston Martin")]
AstonMartin
}

Using extensions and C# 7.3 constraints
public static class EnumMemberExtensions
{
public static string ToEnumString<T>(this T type)
where T : Enum
{
var enumType = typeof(T);
var name = Enum.GetName(enumType, type);
var enumMemberAttribute = ((EnumMemberAttribute[])enumType.GetField(name).GetCustomAttributes(typeof(EnumMemberAttribute), true)).Single();
return enumMemberAttribute.Value;
}
public static T ToEnum<T>(this string str)
where T : Enum
{
var enumType = typeof(T);
foreach (var name in Enum.GetNames(enumType))
{
var enumMemberAttribute = ((EnumMemberAttribute[])enumType.GetField(name).GetCustomAttributes(typeof(EnumMemberAttribute), true)).Single();
if (enumMemberAttribute.Value == str) return (T)Enum.Parse(enumType, name);
}
//throw exception or whatever handling you want or
return default;
}
}

You can use reflection to get the value of the EnumMemberAttribute.
public static string ToEnumString<T>(T instance)
{
if (!typeof(T).IsEnum)
throw new ArgumentException("instance", "Must be enum type");
string enumString = instance.ToString();
var field = typeof(T).GetField(enumString);
if (field != null) // instance can be a number that was cast to T, instead of a named value, or could be a combination of flags instead of a single value
{
var attr = (EnumMemberAttribute)field.GetCustomAttributes(typeof(EnumMemberAttribute), false).SingleOrDefault();
if (attr != null) // if there's no EnumMember attr, use the default value
enumString = attr.Value;
}
return enumString;
}
Depending on how your ToEnum works, you might want to use this sort of approach there as well. Also, the type can be inferred when calling ToEnumString, e.g. SerializerHelper.ToEnumString(status);

This example shows how to convert enums using the DescriptionAttribute, the EnumMemberAttribute and the property name:
using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
public static class EnumExtensions
{
public static T ToEnumByAttributes<T>(this string value)
where T:Enum
{
var enumType = typeof(T);
foreach (var name in Enum.GetNames(enumType))
{
var field = enumType.GetField(name);
if(field == null) continue;
var enumMemberAttribute = GetEnumMemberAttribute(field);
if (enumMemberAttribute != null && enumMemberAttribute.Value == value)
{
return (T)Enum.Parse(enumType, name);
}
var descriptionAttribute = GetDescriptionAttribute(field);
if (descriptionAttribute != null && descriptionAttribute.Description == value)
{
return (T)Enum.Parse(enumType, name);
}
if (name == value)
{
return (T)Enum.Parse(enumType, name);
}
}
throw new ArgumentOutOfRangeException(nameof(value), value, $"The value could not be mapped to type {enumType.FullName}");
}
public static string ToStringByAttributes(this Enum value)
{
var field = value
.GetType()
.GetField(value.ToString());
if (field == null) return string.Empty;
var enumMemberAttribute = GetEnumMemberAttribute(field);
if (enumMemberAttribute != null)
{
return enumMemberAttribute.Value ?? string.Empty;
}
var descriptionAttribute = GetDescriptionAttribute(field);
if (descriptionAttribute != null)
{
return descriptionAttribute.Description;
}
return value.ToString();
}
private static DescriptionAttribute? GetDescriptionAttribute(FieldInfo field)
{
return field
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.OfType<DescriptionAttribute>()
.SingleOrDefault();
}
private static EnumMemberAttribute? GetEnumMemberAttribute(FieldInfo field)
{
return field
.GetCustomAttributes(typeof(EnumMemberAttribute), false)
.OfType<EnumMemberAttribute>()
.SingleOrDefault();
}
}
NUnit Tests:
[TestFixture]
public sealed class EnumExtensionsTests
{
public enum TestEnum
{
[EnumMember(Value = "A")]
Alpha,
[Description("O")]
Omega
}
[Test]
public void ShouldSerialize_FromEnumAttribute()
{
var result = TestEnum.Alpha.ToStringByAttributes();
Assert.That(result, Is.EqualTo("A"));
}
[Test]
public void ShouldSerialize_FromDescriptionAttribute()
{
var result = TestEnum.Omega.ToStringByAttributes();
Assert.That(result, Is.EqualTo("O"));
}
[Test]
public void ShouldDeserialize_FromEnumAttribute()
{
var result = "A".ToEnumByAttributes<TestEnum>();
Assert.That(result, Is.EqualTo(TestEnum.Alpha));
}
[Test]
public void ShouldDeserialize_FromDescriptionAttribute()
{
var result = "O".ToEnumByAttributes<TestEnum>();
Assert.That(result, Is.EqualTo(TestEnum.Omega));
}
[Test]
public void ShouldDeserialize_FromPropertyName()
{
var result = "Alpha".ToEnumByAttributes<TestEnum>();
Assert.That(result, Is.EqualTo(TestEnum.Alpha));
}
}

Related

Trying to get MetadataType by reflect not giving same results if I call the class directly

I'm trying to setup a T4 template that will loop through entity objects and ignore certain navigation properties based on an custom attribute I setup in the class' metdata data.
Here's the setup of the metadata tag
[MetadataType(typeof(ApplicationIntegrationMetadata))]
public partial class ApplicationIntegration
{
}
public class ApplicationIntegrationMetadata
{
[NotToCrud]
public ICollection<Profile> Profiles { get; set; }
}
[AttributeUsage(AttributeTargets.All)]
public class NotToCrud : System.Attribute
{
}
I've created an extension method based that checks if the class has a specific attribute on the property
public static bool CheckIfPropertyIsAnnotated<T>(this object ctx, string propName)
{
var returnValue = false;
if (!string.IsNullOrWhiteSpace(propName))
{
var thisType = ctx.GetType();
var metadataTypes = thisType.GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().ToArray();
var metadata = metadataTypes.FirstOrDefault();
if (metadata != null)
{
var properties = metadata.MetadataClassType.GetProperties();
var found = properties.FirstOrDefault(d => d.Name == propName);
if (found != null)
{
returnValue = Attribute.IsDefined(found, typeof(T));
}
}
}
return returnValue;
}
I've created two separate unit tests to find my issue.
This one works.
[TestCase("Profiles", Result = true, TestName = "Valid property")]
public bool HasAttribute(string propName)
{
var test = new ApplicationIntegration();
var actual = test.CheckIfPropertyIsAnnotated<NotToCrud>(propName);
return actual;
}
This one returns false
[Test]
public void HasAttributeWithAssembly()
{
var myTypes = System.Reflection.Assembly.GetAssembly(typeof(ApplicationIntegration)).GetTypes().Where(d => d.Name == "ApplicationIntegration") .ToList();
foreach (var item in myTypes)
{
var actual = item.CheckIfPropertyIsAnnotated<NotToCrud>("Profiles");
Console.WriteLine($"{item.Name} - {actual.ToString()}");
Assert.AreEqual(true, actual);
}
}
the problem seems to be with (var thisType = ctx.GetType())
On the second test, it returns
object.GetType returned
{Name = "RuntimeType" FullName = "System.RuntimeType"} System.RuntimeType
instead of
{Name = "ApplicationIntegration" FullName = "Apm.Model.ApplicationIntegration"}
Any clue how to get around this?
Because item is a Type (with value typeof(ApplicationIntegration)), not an ApplicationIntegration. You can create an object with Activator.CreateInstance. Like:
var myTypes = System.Reflection.Assembly.GetAssembly(typeof(ApplicationIntegration)).GetTypes().Where(d => d.Name == "ApplicationIntegration") .ToList();
foreach (Type type in myTypes)
{
object obj = Activator.CreateInstance(type);
var actual = obj.CheckIfPropertyIsAnnotated<NotToCrud>("Profiles");

GetType().Name from Class with generic Type parameter [duplicate]

If I write:
var type = typeof(List<string>);
Console.WriteLine(type.Name);
It will write:
List`1
I want it to write just:
List
How can I do that?
Is there a smarter way to do it without having to use Substring or similar string manipulation functions?
No, it makes perfect sense for it to include the generic arity in the name - because it's part of what makes the name unique (along with assembly and namespace, of course).
Put it this way: System.Nullable and System.Nullable<T> are very different types. It's not expected that you'd want to confuse the two... so if you want to lose information, you're going to have to work to do it. It's not very hard, of course, and can be put in a helper method:
public static string GetNameWithoutGenericArity(this Type t)
{
string name = t.Name;
int index = name.IndexOf('`');
return index == -1 ? name : name.Substring(0, index);
}
Then:
var type = typeof(List<string>);
Console.WriteLine(type.GetNameWithoutGenericArity());
No, it doesn't, because the "generic-type-string" is part of the name of type.
If someone is interested, I created some extensionmethods for this problem that create a more "readable" string
it produces something like
List[string]
outer.inner[other.whatever]
IEnumerable[T0]
Dictionary[string:int]
Test here
public static class TypeEx
{
public static string GetTypeName(this Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (!type.IsGenericType)
return type.GetNestedTypeName();
StringBuilder stringBuilder = new StringBuilder();
_buildClassNameRecursiv(type, stringBuilder);
return stringBuilder.ToString();
}
private static void _buildClassNameRecursiv(Type type, StringBuilder classNameBuilder, int genericParameterIndex = 0)
{
if (type.IsGenericParameter)
classNameBuilder.AppendFormat("T{0}", genericParameterIndex + 1);
else if (type.IsGenericType)
{
classNameBuilder.Append(GetNestedTypeName(type) + "[");
int subIndex = 0;
foreach (Type genericTypeArgument in type.GetGenericArguments())
{
if (subIndex > 0)
classNameBuilder.Append(":");
_buildClassNameRecursiv(genericTypeArgument, classNameBuilder, subIndex++);
}
classNameBuilder.Append("]");
}
else
classNameBuilder.Append(type.GetNestedTypeName());
}
public static string GetNestedTypeName(this Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (!type.IsNested)
return type.Name;
StringBuilder nestedName = new StringBuilder();
while(type != null)
{
if(nestedName.Length>0)
nestedName.Insert(0,'.');
nestedName.Insert(0, _getTypeName(type));
type = type.DeclaringType;
}
return nestedName.ToString();
}
private static string _getTypeName(Type type)
{
return type.IsGenericType ? type.Name.Split('`')[0]: type.Name;
}
}
static void Main(string[] args)
{
Console.WriteLine(WhatIsMyType<IEnumerable<string>>());
Console.WriteLine(WhatIsMyType<List<int>>());
Console.WriteLine(WhatIsMyType<IList<int>>());
Console.WriteLine(WhatIsMyType<List<ContentBlob>>());
Console.WriteLine(WhatIsMyType<int[]>());
Console.WriteLine(WhatIsMyType<ContentBlob>());
Console.WriteLine(WhatIsMyType<Dictionary<string, Dictionary<int, int>>>());
}
public static string WhatIsMyType<T>()
{
return typeof(T).NameWithGenerics();
}
public static string NameWithGenerics(this Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (type.IsArray)
return $"{type.GetElementType()?.Name}[]";
if (!type.IsGenericType)
return type.Name;
var name = type.GetGenericTypeDefinition().Name;
var index = name.IndexOf('`');
var newName = index == -1 ? name : name.Substring(0, index);
var list = type.GetGenericArguments().Select(NameWithGenerics).ToList();
return $"{newName}<{string.Join(",", list)}>";
}
Example output:
IEnumerable<String>
List<Int32>
IList<Int32>
List<ContentBlob>
Int32[]
ContentBlob
Dictionary<String,Dictionary<Int32,Int32>>
Here's the code from this answer inside a static class and namespace for easier copy-and-pasting.
Also, there's another extension method to get the type its namespace.
using System;
namespace TODO
{
public static class TypeExtensions
{
/// <summary>
/// From: https://stackoverflow.com/a/6386234/569302
/// </summary>
public static string GetNameWithoutGenericArity(this Type t)
{
string name = t.Name;
int index = name.IndexOf('`');
return index == -1 ? name : name.Substring(0, index);
}
public static string GetFullNameWithoutGenericArity(this Type t)
{
var result = $"{t.Namespace}.{t.GetNameWithoutGenericArity()}";
return result;
}
}
}
The easiest way I can think of as of C#6(I think) you can do the following:
public static void Main(string[] args)
{
Console.WriteLine(nameof(List<int>));
Console.WriteLine(nameof(Dictionary<int, int>));
}
This will print:
List
Dictionary

Enum Description to String

I have the following ENUM:
[Flags]
public enum DataFiat {
[Description("Público")]
Public = 1,
[Description("Filiado")]
Listed = 2,
[Description("Cliente")]
Client = 4
} // DataFiat
And I created an extension to get an Enum attribute:
public static T GetAttribute<T>(this Enum value) where T : Attribute {
T attribute;
MemberInfo info = value.GetType().GetMember(value.ToString()).FirstOrDefault();
if (info != null) {
attribute = (T)info.GetCustomAttributes(typeof(T), false).FirstOrDefault();
return attribute;
}
return null;
}
This works for non Flags Enums ... But when I have:
var x = DataFiat.Public | DataFiat.Listed;
var y = x.GetAttribute<Description>();
The value of y is null ...
I would like to get "Público, Filiado, Cliente" ... Just as ToString() works.
How can I change my extension to make this work?
Thank You
You can use this:
var values = x.ToString()
.Split(new[] { ", " }, StringSplitOptions.None)
.Select(v => (DataFiat)Enum.Parse(typeof(DataFiat), v));
To get the individual values. Then get the attribute values of them.
Something like this:
var y2 = values.GetAttributes<DescriptionAttribute, DataFiat>();
public static T[] GetAttributes<T, T2>(this IEnumerable<T2> values) where T : Attribute
{
List<T> ts =new List<T>();
foreach (T2 value in values)
{
T attribute;
MemberInfo info = value.GetType().GetMember(value.ToString()).FirstOrDefault();
if (info != null)
{
attribute = (T)info.GetCustomAttributes(typeof(T), false).FirstOrDefault();
ts.Add(attribute);
}
}
return ts.ToArray();
}
in .NET CORE without any additional libraries you can do:
public enum Divisions
{
[Display(Name = "My Title 1")]
None,
[Display(Name = "My Title 2")]
First,
}
and to get the title:
using System.ComponentModel.DataAnnotations
using System.Reflection
string title = enumValue.GetType()?.GetMember(enumValue.ToString())?[0]?.GetCustomAttribute<DisplayAttribute>()?.Name;
I think you want to make something like that
using System;
public enum ArrivalStatus { Unknown=-3, Late=-1, OnTime=0, Early=1 };
public class Example
{
public static void Main()
{
int[] values = { -3, -1, 0, 1, 5, Int32.MaxValue };
foreach (var value in values)
{
ArrivalStatus status;
if (Enum.IsDefined(typeof(ArrivalStatus), value))
status = (ArrivalStatus) value;
else
status = ArrivalStatus.Unknown;
Console.WriteLine("Converted {0:N0} to {1}", value, status);
}
}
}
// The example displays the following output:
// Converted -3 to Unknown
// Converted -1 to Late
// Converted 0 to OnTime
// Converted 1 to Early
// Converted 5 to Unknown
// Converted 2,147,483,647 to Unknown
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
public static class Program
{
[Flags]
public enum DataFiat
{
[Description("Público")]
Public = 1,
[Description("Filiado")]
Listed = 2,
[Description("Cliente")]
Client = 4
}
public static ICollection<string> GetAttribute<T>(this Enum value)
{
var result = new Collection<string>();
var type = typeof(DataFiat);
foreach (var value1 in Enum.GetValues(type))
{
var memInfo = type.GetMember(value1.ToString());
var attributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
var description = ((DescriptionAttribute)attributes[0]).Description;
result.Add(description);
}
return result;
}
static void Main(string[] args)
{
var x = DataFiat.Public | DataFiat.Listed;
var y = x.GetAttribute<DataFiat>();
var output = string.Join(" ", y.ToArray());
Console.WriteLine(output);
}
}
I have changed the T to ICollection but you can change it as you wish or you can merege the data within the method and return the string back.
I came up with a different solution based on my previous code. It can be used as follows:
DataFiat fiat = DataFiat.Public | DataFiat.Listed;
var b = fiat.ToString();
var c = fiat.GetAttributes<TextAttribute>();
var d = fiat.GetAttributes<TextAttribute, String>(x => String.Join(",", x.Select(y => y.Value)));
I think it becomes easy to use either to get the attributes or doing something with them.
What do you think?
Let me know if the code can be somehow improved. Here is the code:
public static List<T> GetAttributes<T>(this Enum value) where T : Attribute {
List<T> attributes = new List<T>();
IEnumerable<Enum> flags = Enum.GetValues(value.GetType()).Cast<Enum>().Where(value.HasFlag);
if (flags != null) {
foreach (Enum flag in flags) {
MemberInfo info = flag.GetType().GetMember(flag.ToString()).FirstOrDefault();
if (info != null)
attributes.Add((T)info.GetCustomAttributes(typeof(T), false).FirstOrDefault());
}
return attributes;
}
return null;
} // GetAttributes
public static Expected GetAttributes<T, Expected>(this Enum value, Func<List<T>, Expected> expression) where T : Attribute {
List<T> attributes = value.GetAttributes<T>();
if (attributes == null)
return default(Expected);
return expression(attributes);
} // GetAttributes

How do you give a C# Auto-Property a default value using a custom attribute?

How do you give a C# Auto-Property a default value, using a custom attribute?
This is the code I want to see:
class Person
{
[MyDefault("William")]
public string Name { get; set; }
}
I am aware that there is no built in method to initialize the default using an attribute - can I write my own custom class that uses my custom attributes to initialize the default?
If you want to do it with PostSharp (as your tags suggest) then use a Lazy Loading aspect. You can see the one I built here http://programmersunlimited.wordpress.com/2011/03/23/postsharp-weaving-community-vs-professional-reasons-to-get-a-professional-license/
With an aspect you can apply default value to a single property or apply it to multiple properties with a single declaration at the class level.
Lazy loading aspect will use LocationInterceptionAspect base class.
[Serializable]
[LazyLoadingAspect(AttributeExclude=true)]
[MulticastAttributeUsage(MulticastTargets.Property)]
public class LazyLoadingAspectAttribute : LocationInterceptionAspect
{
public object DefaultValue {get; set;}
public override void OnGetValue(LocationInterceptionArgs args)
{
args.ProceedGetValue();
if (args.Value != null)
{
return;
}
args.Value = DefaultValue;
args.ProceedSetValue();
}
}
then apply the aspect like so
[LazyLoadingAspect(DefaultValue="SomeValue")]
public string MyProp { get; set; }
You could use a helper class like that:
public class DefaultValueHelper
{
public static void InitializeDefaultValues<T>(T obj)
{
var properties =
(from prop in obj.GetType().GetProperties()
let attr = GetDefaultValueAttribute(prop)
where attr != null
select new
{
Property = prop,
DefaultValue = attr.Value
}).ToArray();
foreach (var p in properties)
{
p.Property.SetValue(obj, p.DefaultValue, null);
}
}
private static DefaultValueAttribute GetDefaultValueAttribute(PropertyInfo prop)
{
return prop.GetCustomAttributes(typeof(DefaultValueAttribute), true)
.Cast<DefaultValueAttribute>()
.FirstOrDefault();
}
}
And call InitializeDefaultValues in the constructor of your class.
class Foo
{
public Foo()
{
DefaultValueHelper.InitializeDefaultValues(this);
}
[DefaultValue("(no name)")]
public string Name { get; set; }
}
EDIT: updated version, which generates and caches a delegate to do the initialization. This is to avoid using reflection every time the method is called for a given type.
public static class DefaultValueHelper
{
private static readonly Dictionary<Type, Action<object>> _initializerCache;
static DefaultValueHelper()
{
_initializerCache = new Dictionary<Type, Action<object>>();
}
public static void InitializeDefaultValues(object obj)
{
if (obj == null)
return;
var type = obj.GetType();
Action<object> initializer;
if (!_initializerCache.TryGetValue(type, out initializer))
{
initializer = MakeInitializer(type);
_initializerCache[type] = initializer;
}
initializer(obj);
}
private static Action<object> MakeInitializer(Type type)
{
var arg = Expression.Parameter(typeof(object), "arg");
var variable = Expression.Variable(type, "x");
var cast = Expression.Assign(variable, Expression.Convert(arg, type));
var assignments =
from prop in type.GetProperties()
let attr = GetDefaultValueAttribute(prop)
where attr != null
select Expression.Assign(Expression.Property(variable, prop), Expression.Constant(attr.Value));
var body = Expression.Block(
new ParameterExpression[] { variable },
new Expression[] { cast }.Concat(assignments));
var expr = Expression.Lambda<Action<object>>(body, arg);
return expr.Compile();
}
private static DefaultValueAttribute GetDefaultValueAttribute(PropertyInfo prop)
{
return prop.GetCustomAttributes(typeof(DefaultValueAttribute), true)
.Cast<DefaultValueAttribute>()
.FirstOrDefault();
}
}
If to speculate with Expressions you could make initializing delegates and cache them. It will make code much faster comparing with just pure reflection.
internal static class Initializer
{
private class InitCacheEntry
{
private Action<object, object>[] _setters;
private object[] _values;
public InitCacheEntry(IEnumerable<Action<object, object>> setters, IEnumerable<object> values)
{
_setters = setters.ToArray();
_values = values.ToArray();
if (_setters.Length != _values.Length)
throw new ArgumentException();
}
public void Init(object obj)
{
for (int i = 0; i < _setters.Length; i++)
{
_setters[i](obj, _values[i]);
}
}
}
private static Dictionary<Type, InitCacheEntry> _cache = new Dictionary<Type, InitCacheEntry>();
private static InitCacheEntry MakeCacheEntry(Type targetType)
{
var setters = new List<Action<object, object>>();
var values = new List<object>();
foreach (var propertyInfo in targetType.GetProperties())
{
var attr = (DefaultAttribute) propertyInfo.GetCustomAttributes(typeof (DefaultAttribute), true).FirstOrDefault();
if (attr == null) continue;
var setter = propertyInfo.GetSetMethod();
if (setter == null) continue;
// we have to create expression like (target, value) => ((TObj)target).setter((T)value)
// where T is the type of property and obj is instance being initialized
var targetParam = Expression.Parameter(typeof (object), "target");
var valueParam = Expression.Parameter(typeof (object), "value");
var expr = Expression.Lambda<Action<object, object>>(
Expression.Call(Expression.Convert(targetParam, targetType),
setter,
Expression.Convert(valueParam, propertyInfo.PropertyType)),
targetParam, valueParam);
var set = expr.Compile();
setters.Add(set);
values.Add(attr.DefaultValue);
}
return new InitCacheEntry(setters, values);
}
public static void Init(object obj)
{
Type targetType = obj.GetType();
InitCacheEntry init;
if (!_cache.TryGetValue(targetType, out init))
{
init = MakeCacheEntry(targetType);
_cache[targetType] = init;
}
init.Init(obj);
}
}
You could create a method like this:
public static void FillProperties<T>(T obj)
{
foreach (var property in typeof(T).GetProperties())
{
var attribute = property
.GetCustomAttributes(typeof(DefaultValueAttribute), true)
.Cast<DefaultValueAttribute>()
.SingleOrDefault();
if (attribute != null)
property.SetValue(obj, attribute.Value, null);
}
}
You can then either use a factory method that calls this method or call it directly from the constructor. Note that this usage of reflection is probably not a good idea if you create a lot of objects this way and performance is important.

Get Enum from Description attribute [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Finding an enum value by its Description Attribute
I have a generic extension method which gets the Description attribute from an Enum:
enum Animal
{
[Description("")]
NotSet = 0,
[Description("Giant Panda")]
GiantPanda = 1,
[Description("Lesser Spotted Anteater")]
LesserSpottedAnteater = 2
}
public static string GetDescription(this Enum value)
{
FieldInfo field = value.GetType().GetField(value.ToString());
DescriptionAttribute attribute
= Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute))
as DescriptionAttribute;
return attribute == null ? value.ToString() : attribute.Description;
}
so I can do...
string myAnimal = Animal.GiantPanda.GetDescription(); // = "Giant Panda"
now, I'm trying to work out the equivalent function in the other direction, something like...
Animal a = (Animal)Enum.GetValueFromDescription("Giant Panda", typeof(Animal));
public static class EnumEx
{
public static T GetValueFromDescription<T>(string description) where T : Enum
{
foreach(var field in typeof(T).GetFields())
{
if (Attribute.GetCustomAttribute(field,
typeof(DescriptionAttribute)) is DescriptionAttribute attribute)
{
if (attribute.Description == description)
return (T)field.GetValue(null);
}
else
{
if (field.Name == description)
return (T)field.GetValue(null);
}
}
throw new ArgumentException("Not found.", nameof(description));
// Or return default(T);
}
}
Usage:
var panda = EnumEx.GetValueFromDescription<Animal>("Giant Panda");
rather than extension methods, just try a couple of static methods
public static class Utility
{
public static string GetDescriptionFromEnumValue(Enum value)
{
DescriptionAttribute attribute = value.GetType()
.GetField(value.ToString())
.GetCustomAttributes(typeof (DescriptionAttribute), false)
.SingleOrDefault() as DescriptionAttribute;
return attribute == null ? value.ToString() : attribute.Description;
}
public static T GetEnumValueFromDescription<T>(string description)
{
var type = typeof(T);
if (!type.IsEnum)
throw new ArgumentException();
FieldInfo[] fields = type.GetFields();
var field = fields
.SelectMany(f => f.GetCustomAttributes(
typeof(DescriptionAttribute), false), (
f, a) => new { Field = f, Att = a })
.Where(a => ((DescriptionAttribute)a.Att)
.Description == description).SingleOrDefault();
return field == null ? default(T) : (T)field.Field.GetRawConstantValue();
}
}
and use here
var result1 = Utility.GetDescriptionFromEnumValue(
Animal.GiantPanda);
var result2 = Utility.GetEnumValueFromDescription<Animal>(
"Lesser Spotted Anteater");
The solution works good except if you have a Web Service.
You would need to do the Following as the Description Attribute is not serializable.
[DataContract]
public enum ControlSelectionType
{
[EnumMember(Value = "Not Applicable")]
NotApplicable = 1,
[EnumMember(Value = "Single Select Radio Buttons")]
SingleSelectRadioButtons = 2,
[EnumMember(Value = "Completely Different Display Text")]
SingleSelectDropDownList = 3,
}
public static string GetDescriptionFromEnumValue(Enum value)
{
EnumMemberAttribute attribute = value.GetType()
.GetField(value.ToString())
.GetCustomAttributes(typeof(EnumMemberAttribute), false)
.SingleOrDefault() as EnumMemberAttribute;
return attribute == null ? value.ToString() : attribute.Value;
}
Should be pretty straightforward, its just the reverse of your previous method;
public static int GetEnumFromDescription(string description, Type enumType)
{
foreach (var field in enumType.GetFields())
{
DescriptionAttribute attribute
= Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute))as DescriptionAttribute;
if(attribute == null)
continue;
if(attribute.Description == description)
{
return (int) field.GetValue(null);
}
}
return 0;
}
Usage:
Console.WriteLine((Animal)GetEnumFromDescription("Giant Panda",typeof(Animal)));
You can't extend Enum as it's a static class. You can only extend instances of a type. With this in mind, you're going to have to create a static method yourself to do this; the following should work when combined with your existing method GetDescription:
public static class EnumHelper
{
public static T GetEnumFromString<T>(string value)
{
if (Enum.IsDefined(typeof(T), value))
{
return (T)Enum.Parse(typeof(T), value, true);
}
else
{
string[] enumNames = Enum.GetNames(typeof(T));
foreach (string enumName in enumNames)
{
object e = Enum.Parse(typeof(T), enumName);
if (value == GetDescription((Enum)e))
{
return (T)e;
}
}
}
throw new ArgumentException("The value '" + value
+ "' does not match a valid enum name or description.");
}
}
And the usage of it would be something like this:
Animal giantPanda = EnumHelper.GetEnumFromString<Animal>("Giant Panda");
You need to iterate through all the enum values in Animal and return the value that matches the description you need.

Categories