Related
I have a class, lets call it Book with a property called Name. With that property, I have an attribute associated with it.
public class Book
{
[Author("AuthorName")]
public string Name
{
get; private set;
}
}
In my main method, I'm using reflection and wish to get key value pair of each attribute for each property. So in this example, I'd expect to see "Author" for attribute name and "AuthorName" for the attribute value.
Question: How do I get the attribute name and value on my properties using Reflection?
Use typeof(Book).GetProperties() to get an array of PropertyInfo instances. Then use GetCustomAttributes() on each PropertyInfo to see if any of them have the Author Attribute type. If they do, you can get the name of the property from the property info and the attribute values from the attribute.
Something along these lines to scan a type for properties that have a specific attribute type and to return data in a dictionary (note that this can be made more dynamic by passing types into the routine):
public static Dictionary<string, string> GetAuthors()
{
Dictionary<string, string> _dict = new Dictionary<string, string>();
PropertyInfo[] props = typeof(Book).GetProperties();
foreach (PropertyInfo prop in props)
{
object[] attrs = prop.GetCustomAttributes(true);
foreach (object attr in attrs)
{
AuthorAttribute authAttr = attr as AuthorAttribute;
if (authAttr != null)
{
string propName = prop.Name;
string auth = authAttr.Name;
_dict.Add(propName, auth);
}
}
}
return _dict;
}
To get all attributes of a property in a dictionary use this:
typeof(Book)
.GetProperty("Name")
.GetCustomAttributes(false)
.ToDictionary(a => a.GetType().Name, a => a);
remember to change from false to true if you want to include inheritted attributes as well.
If you just want one specific Attribute value For instance Display Attribute you can use the following code:
var pInfo = typeof(Book).GetProperty("Name")
.GetCustomAttribute<DisplayAttribute>();
var name = pInfo.Name;
I have solved similar problems by writing a Generic Extension Property Attribute Helper:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
public static class AttributeHelper
{
public static TValue GetPropertyAttributeValue<T, TOut, TAttribute, TValue>(
Expression<Func<T, TOut>> propertyExpression,
Func<TAttribute, TValue> valueSelector)
where TAttribute : Attribute
{
var expression = (MemberExpression) propertyExpression.Body;
var propertyInfo = (PropertyInfo) expression.Member;
var attr = propertyInfo.GetCustomAttributes(typeof(TAttribute), true).FirstOrDefault() as TAttribute;
return attr != null ? valueSelector(attr) : default(TValue);
}
}
Usage:
var author = AttributeHelper.GetPropertyAttributeValue<Book, string, AuthorAttribute, string>(prop => prop.Name, attr => attr.Author);
// author = "AuthorName"
You can use GetCustomAttributesData() and GetCustomAttributes():
var attributeData = typeof(Book).GetProperty("Name").GetCustomAttributesData();
var attributes = typeof(Book).GetProperty("Name").GetCustomAttributes(false);
If you mean "for attributes that take one parameter, list the attribute-names and the parameter-value", then this is easier in .NET 4.5 via the CustomAttributeData API:
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
public static class Program
{
static void Main()
{
PropertyInfo prop = typeof(Foo).GetProperty("Bar");
var vals = GetPropertyAttributes(prop);
// has: DisplayName = "abc", Browsable = false
}
public static Dictionary<string, object> GetPropertyAttributes(PropertyInfo property)
{
Dictionary<string, object> attribs = new Dictionary<string, object>();
// look for attributes that takes one constructor argument
foreach (CustomAttributeData attribData in property.GetCustomAttributesData())
{
if(attribData.ConstructorArguments.Count == 1)
{
string typeName = attribData.Constructor.DeclaringType.Name;
if (typeName.EndsWith("Attribute")) typeName = typeName.Substring(0, typeName.Length - 9);
attribs[typeName] = attribData.ConstructorArguments[0].Value;
}
}
return attribs;
}
}
class Foo
{
[DisplayName("abc")]
[Browsable(false)]
public string Bar { get; set; }
}
private static Dictionary<string, string> GetAuthors()
{
return typeof(Book).GetProperties()
.SelectMany(prop => prop.GetCustomAttributes())
.OfType<AuthorAttribute>()
.ToDictionary(a => a.GetType().Name.Replace("Attribute", ""), a => a.Name);
}
Example using generics (target framework 4.5)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
private static Dictionary<string, string> GetAttribute<TAttribute, TType>(
Func<TAttribute, string> valueFunc)
where TAttribute : Attribute
{
return typeof(TType).GetProperties()
.SelectMany(p => p.GetCustomAttributes())
.OfType<TAttribute>()
.ToDictionary(a => a.GetType().Name.Replace("Attribute", ""), valueFunc);
}
Usage
var dictionary = GetAttribute<AuthorAttribute, Book>(a => a.Name);
public static class PropertyInfoExtensions
{
public static TValue GetAttributValue<TAttribute, TValue>(this PropertyInfo prop, Func<TAttribute, TValue> value) where TAttribute : Attribute
{
var att = prop.GetCustomAttributes(
typeof(TAttribute), true
).FirstOrDefault() as TAttribute;
if (att != null)
{
return value(att);
}
return default(TValue);
}
}
Usage:
//get class properties with attribute [AuthorAttribute]
var props = typeof(Book).GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(AuthorAttribute)));
foreach (var prop in props)
{
string value = prop.GetAttributValue((AuthorAttribute a) => a.Name);
}
or:
//get class properties with attribute [AuthorAttribute]
var props = typeof(Book).GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(AuthorAttribute)));
IList<string> values = props.Select(prop => prop.GetAttributValue((AuthorAttribute a) => a.Name)).Where(attr => attr != null).ToList();
While the above most upvoted answers definitely work, I'd suggest using a slightly different approach in some cases.
If your class has multiple properties with always the same attribute and you want to get those attributes sorted into a dictionary, here is how:
var dict = typeof(Book).GetProperties().ToDictionary(p => p.Name, p => p.GetCustomAttributes(typeof(AuthorName), false).Select(a => (AuthorName)a).FirstOrDefault());
This still uses cast but ensures that the cast will always work as you will only get the custom attributes of the type "AuthorName".
If you had multiple Attributes above answers would get a cast exception.
Here are some static methods you can use to get the MaxLength, or any other attribute.
using System;
using System.Linq;
using System.Reflection;
using System.ComponentModel.DataAnnotations;
using System.Linq.Expressions;
public static class AttributeHelpers {
public static Int32 GetMaxLength<T>(Expression<Func<T,string>> propertyExpression) {
return GetPropertyAttributeValue<T,string,MaxLengthAttribute,Int32>(propertyExpression,attr => attr.Length);
}
//Optional Extension method
public static Int32 GetMaxLength<T>(this T instance,Expression<Func<T,string>> propertyExpression) {
return GetMaxLength<T>(propertyExpression);
}
//Required generic method to get any property attribute from any class
public static TValue GetPropertyAttributeValue<T, TOut, TAttribute, TValue>(Expression<Func<T,TOut>> propertyExpression,Func<TAttribute,TValue> valueSelector) where TAttribute : Attribute {
var expression = (MemberExpression)propertyExpression.Body;
var propertyInfo = (PropertyInfo)expression.Member;
var attr = propertyInfo.GetCustomAttributes(typeof(TAttribute),true).FirstOrDefault() as TAttribute;
if (attr==null) {
throw new MissingMemberException(typeof(T).Name+"."+propertyInfo.Name,typeof(TAttribute).Name);
}
return valueSelector(attr);
}
}
Using the static method...
var length = AttributeHelpers.GetMaxLength<Player>(x => x.PlayerName);
Or using the optional extension method on an instance...
var player = new Player();
var length = player.GetMaxLength(x => x.PlayerName);
Or using the full static method for any other attribute (StringLength for example)...
var length = AttributeHelpers.GetPropertyAttributeValue<Player,string,StringLengthAttribute,Int32>(prop => prop.PlayerName,attr => attr.MaximumLength);
Inspired by the Mikael Engver's answer.
I wrote this into a dynamic method since I use lots of attributes throughout my application. Method:
public static dynamic GetAttribute(Type objectType, string propertyName, Type attrType)
{
//get the property
var property = objectType.GetProperty(propertyName);
//check for object relation
return property.GetCustomAttributes().FirstOrDefault(x => x.GetType() == attrType);
}
Usage:
var objectRelAttr = GetAttribute(typeof(Person), "Country", typeof(ObjectRelationAttribute));
var displayNameAttr = GetAttribute(typeof(Product), "Category", typeof(DisplayNameAttribute));
Hope this helps anyone
Necromancing.
For those that still have to maintain .NET 2.0, or those that want to do it without LINQ:
public static object GetAttribute(System.Reflection.MemberInfo mi, System.Type t)
{
object[] objs = mi.GetCustomAttributes(t, true);
if (objs == null || objs.Length < 1)
return null;
return objs[0];
}
public static T GetAttribute<T>(System.Reflection.MemberInfo mi)
{
return (T)GetAttribute(mi, typeof(T));
}
public delegate TResult GetValue_t<in T, out TResult>(T arg1);
public static TValue GetAttributValue<TAttribute, TValue>(System.Reflection.MemberInfo mi, GetValue_t<TAttribute, TValue> value) where TAttribute : System.Attribute
{
TAttribute[] objAtts = (TAttribute[])mi.GetCustomAttributes(typeof(TAttribute), true);
TAttribute att = (objAtts == null || objAtts.Length < 1) ? default(TAttribute) : objAtts[0];
// TAttribute att = (TAttribute)GetAttribute(mi, typeof(TAttribute));
if (att != null)
{
return value(att);
}
return default(TValue);
}
Example usage:
System.Reflection.FieldInfo fi = t.GetField("PrintBackground");
wkHtmlOptionNameAttribute att = GetAttribute<wkHtmlOptionNameAttribute>(fi);
string name = GetAttributValue<wkHtmlOptionNameAttribute, string>(fi, delegate(wkHtmlOptionNameAttribute a){ return a.Name;});
or simply
string aname = GetAttributValue<wkHtmlOptionNameAttribute, string>(fi, a => a.Name );
Just looking for the right place to put this piece of code.
let's say you have the following property:
[Display(Name = "Solar Radiation (Average)", ShortName = "SolarRadiationAvg")]
public int SolarRadiationAvgSensorId { get; set; }
And you want to get the ShortName value. You can do:
((DisplayAttribute)(typeof(SensorsModel).GetProperty(SolarRadiationAvgSensorId).GetCustomAttribute(typeof(DisplayAttribute)))).ShortName;
Or to make it general:
internal static string GetPropertyAttributeShortName(string propertyName)
{
return ((DisplayAttribute)(typeof(SensorsModel).GetProperty(propertyName).GetCustomAttribute(typeof(DisplayAttribute)))).ShortName;
}
foreach (var p in model.GetType().GetProperties())
{
var valueOfDisplay =
p.GetCustomAttributesData()
.Any(a => a.AttributeType.Name == "DisplayNameAttribute") ?
p.GetCustomAttribute<DisplayNameAttribute>().DisplayName :
p.Name;
}
In this example I used DisplayName instead of Author because it has a field named 'DisplayName' to be shown with a value.
to get attribute from enum, i'm using :
public enum ExceptionCodes
{
[ExceptionCode(1000)]
InternalError,
}
public static (int code, string message) Translate(ExceptionCodes code)
{
return code.GetType()
.GetField(Enum.GetName(typeof(ExceptionCodes), code))
.GetCustomAttributes(false).Where((attr) =>
{
return (attr is ExceptionCodeAttribute);
}).Select(customAttr =>
{
var attr = (customAttr as ExceptionCodeAttribute);
return (attr.Code, attr.FriendlyMessage);
}).FirstOrDefault();
}
// Using
var _message = Translate(code);
If you want get property having the custom Attribute then please try the following:
IEnumerable propertyInfos = properties.GetType().GetProperties();
PropertyInfo p = propertyInfos.Where(x => x.GetCustomAttribute() != null);
public enum Status
{
Pending,
[EnumMember(Value = "In Progress")]
InProgress,
Failed,
Success
}
string dbValue = "In Progress";
if (dbValue == ValueOf(Status.InProgress)){
//do some thing
}
How do I read the Value of Status.InProgress so I get back "in Progress"?
This is an extension method that works with C# 8 and nullable reference types:
public static string? GetEnumMemberValue<T>(this T value)
where T : Enum
{
return typeof(T)
.GetTypeInfo()
.DeclaredMembers
.SingleOrDefault(x => x.Name == value.ToString())
?.GetCustomAttribute<EnumMemberAttribute>(false)
?.Value;
}
Original Answer:
I've adapted this for .NET Core. Here it is:
public static String GetEnumMemberValue<T>(T value)
where T : struct, IConvertible
{
return typeof(T)
.GetTypeInfo()
.DeclaredMembers
.SingleOrDefault(x => x.Name == value.ToString())
?.GetCustomAttribute<EnumMemberAttribute>(false)
?.Value;
}
Something like this:
public string GetEnumMemberAttrValue(Type enumType, object enumVal)
{
var memInfo = enumType.GetMember(enumVal.ToString());
var attr = memInfo[0].GetCustomAttributes(false).OfType<EnumMemberAttribute>().FirstOrDefault();
if(attr != null)
{
return attr.Value;
}
return null;
}
Usage:
var enumType = typeof(Status);
var enumVal = Status.InProgress;
var str = GetEnumMemberAttrValue(enumType,enumVal);
Borrowing from Amir's answer, a slightly nicer version is possible using generics as follows:
public string GetEnumMemberAttrValue<T>(T enumVal)
{
var enumType = typeof(T);
var memInfo = enumType.GetMember(enumVal.ToString());
var attr = memInfo.FirstOrDefault()?.GetCustomAttributes(false).OfType<EnumMemberAttribute>().FirstOrDefault();
if (attr != null)
{
return attr.Value;
}
return null;
}
Usage as follows:
var enumVal = Status.InProgress;
var str = GetEnumMemberAttrValue(enumVal);
As far as I'm aware T can't be constrained to enum's using a where clause. I would be happy to be corrected though.
Wrapped it in an extension to make it feel more natural:
public static class Extension
{
public static string ToEnumMemberAttrValue(this Enum #enum)
{
var attr =
#enum.GetType().GetMember(#enum.ToString()).FirstOrDefault()?.
GetCustomAttributes(false).OfType<EnumMemberAttribute>().
FirstOrDefault();
if (attr == null)
return #enum.ToString();
return attr.Value;
}
}
Usage:
string dbValue = "In Progress";
if (dbValue == Status.ToEnumMemberAttrValue())){
//do some thing
}
If you have Newtonsoft in your project, that is what you should do:
to the Enum, you should add the attribute [JsonConverter(typeof(StringEnumConverter))]
and now you can call the JsonConvertor to serialize your value as the member string value.
in your example, it should be like that
[JsonConverter(typeof(StringEnumConverter))]
public enum Status
{
Pending,
[EnumMember(Value = "In Progress")]
InProgress,
Failed,
Success
}
string dbValue = "In Progress";
if (dbValue == JsonConvert.SerializeObject(Status.InProgress)){
//do some thing
}
Note as mentioned by #Dinesh in the comment that the string is JSON therefor return with quatos so you can workaround that to get a clean string with strin.Replace method as:
dbValue == JsonConvert.SerializeObject(Status.InProgress).Replace("\"","");
public static object GetMemberAttr(this Enum enumItem)
{
var memInfo = enumItem.GetType().GetMember(enumItem.ToString());
var attr = memInfo[0].GetCustomAttributes(false);
return attr == null || attr.Length == 0 ? null :((System.Runtime.Serialization.EnumMemberAttribute) attr[0]).Value;
}
Usage: {YouEnum}.{EnumItem}.GetMemberAttr()
public enum TEST_ENUM
{
[EnumMember(Value = "1min")]
Minute,
[EnumMember(Value = "2min")]
TwoMinutes,
[EnumMember(Value = "3min")]
ThreeMinutes,
}
public TEST_ENUM t;
? t.TwoMinutes.GetMemberAttr()
2min
Try This ,
var type = typeof(YourEnum);
var Info = type.GetMember(YourEnum.attribute); // pass enum item here
string enumdescription = Info[0].CustomAttributes.SingleOrDefault().NamedArguments[0].TypedValue.ToString();
Status.InProgress.GetStringValue()
If you have the Elasticsearch.Net library you can use this, but the other solutions here are probably better if you dont happen to already be using ES.
[EnumMember] attribute is used by a serializer such as Newtonsoft.
System.Runtime.Serialization (namespace)
EnumMemberAttribute (class)
Enum:
public enum Status
{
Pending,
[EnumMember(Value = "In Progress")]
InProgress,
Failed,
Success
}
Example code:
string databaseValue = "In Progress";
// Serialize the enum value
string statusValue = Newtonsoft.Json.JsonConvert.SerializeObject(Status.InProgress);
if (statusValue.Contains(databaseValue))
{
// Do something
}
A problem with all of the approaches post so far is that they use GetCustomAttribute<EnumMemberAttribute> for every lookup, which is somewhat expensive.
Given that attributes loaded from reflection are immutable it makes sense to cache the GetCustomAttribute<> result in-memory for each enum type (TEnum) being looked-up.
A static class<T> that's generic over T with a static initializer effectively acts as a singleton for every T, which can be used to own an ImmutableDictionary to efficiently and lazily cache the enum member attribute data:
Because the code below is generic over TEnum : struct, Enum it means there is also no boxing of enum values and it supports enums of varying underlying-type (e.g. enum Foo : int, enum Bar : long, etc):
public static class EnumMemberNames
{
public static String? GetNameOrNull<TEnum>( TEnum value ) => EnumAttribCache<TEnum>.cachedNames.TryGetValue( value, out String? text ) ? text : null;
public static String GetName<TEnum>( TEnum value ) => GetNameOrNull( value ) ?? value.ToString();
private static class EnumAttribCache<TEnum>
where TEnum : struct, Enum
{
public static readonly ImmutableDictionary<TEnum,String> cachedNames = LoadNames();
private static ImmutableDictionary<TEnum,String> LoadNames()
{
return typeof(TEnum)
.GetTypeInfo()
.DeclaredFields
.Where( f => f.IsStatic && f.IsPublic && f.FieldType == typeof(TEnum) )
.Select( f => ( field: f, attrib: f.GetCustomAttribute<EnumMemberAttribute>() ) )
.Where( t => ( t.attrib?.IsValueSetExplicitly ?? false ) && !String.IsNullOrEmpty( t.attrib.Value ) )
.ToDictionary(
keySelector : t => (TEnum)t.field.GetValue( obj: null )!,
elementSelector: t => t.attrib!.Value!
)
.ToImmutableDictionary();
}
}
}
Used like so:
enum Foo
{
[EnumMemberAttribute( Value = "first" )]
A = 1,
[EnumMemberAttribute( Value = "second" )]
B = 2,
Unnamed = 4
}
public static void Main()
{
Console.WriteLine( EnumMemberNames.GetNameOrNull( Foo.A ) ); // "first"
Console.WriteLine( EnumMemberNames.GetNameOrNull( Foo.B ) ); // "second"
Console.WriteLine( EnumMemberNames.GetName( Foo.Unnamed ) ); // "Unnamed"
}
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
Would like to be able to populate any properties of an object and search a collection for objects that match the given properties.
class Program
{
static List<Marble> marbles = new List<Marble> {
new Marble {Color = "Red", Size = 3},
new Marble {Color = "Green", Size = 4},
new Marble {Color = "Black", Size = 6}
};
static void Main()
{
var search1 = new Marble { Color = "Green" };
var search2 = new Marble { Size = 6 };
var results = SearchMarbles(search1);
}
public static IEnumerable<Marble> SearchMarbles(Marble search)
{
var results = from marble in marbles
//where ???
//Search for marbles with whatever property matches the populated properties of the parameter
//In this example it would return just the 'Green' marble
select marble;
return results;
}
public class Marble
{
public string Color { get; set; }
public int Size { get; set; }
}
}
Admittedly, it is interesting and take me time. First, you need to get all properties of search object which have value different with default value, this method is generic using reflection:
var properties = typeof (Marble).GetProperties().Where(p =>
{
var pType = p.PropertyType;
var defaultValue = pType.IsValueType
? Activator.CreateInstance(pType) : null;
var recentValue = p.GetValue(search);
return !recentValue.Equals(defaultValue);
});
Then you can use LINQ All to filter:
var results = marbles.Where(m =>
properties.All(p =>
typeof (Marble).GetProperty(p.Name)
.GetValue(m) == p.GetValue(search)));
P.s: This code has been tested
I am going to propose the generic solution which will work with any number of properties and with any object. It will also be usable in Linq-To-Sql context - it will translate well to sql.
First, start by defining function which will test if the given value is to be treated as a non-set, e.g:
static public bool IsDefault(object o)
{
return o == null || o.GetType().IsValueType && Activator.CreateInstance(o.GetType()).Equals(o);
}
Then, we will have a function which constructs a Lambda expression with test against the values of all set properties in search object:
static public Expression<Func<T, bool>> GetComparison<T>(T search)
{
var param = Expression.Parameter(typeof(T), "t");
var props = from p in typeof(T).GetProperties()
where p.CanRead && !IsDefault(p.GetValue(search, null))
select Expression.Equal(
Expression.Property(param, p.Name),
Expression.Constant(p.GetValue(search, null))
);
var expr = props.Aggregate((a, b) => Expression.AndAlso(a, b));
var lambda = Expression.Lambda<Func<T, bool>>(expr, param);
return lambda;
}
We can use it on any IQueryable:
public static IEnumerable<Marble> SearchMarbles (Marble search)
{
var results = marbles.AsQueryable().Where(GetComparison(search));
return results.AsEnumerable();
}
You can use a separate Filter class like this:
class Filter
{
public string PropertyName { get; set; }
public object PropertyValue { get; set; }
public bool Matches(Marble m)
{
var T = typeof(Marble);
var prop = T.GetProperty(PropertyName);
var value = prop.GetValue(m);
return value.Equals(PropertyValue);
}
}
You can use this Filter as follows:
var filters = new List<Filter>();
filters.Add(new Filter() { PropertyName = "Color", PropertyValue = "Green" });
//this is essentially the content of SearchMarbles()
var result = marbles.Where(m => filters.All(f => f.Matches(m)));
foreach (var r in result)
{
Console.WriteLine(r.Color + ", " + r.Size);
}
You could use DependencyProperties to get rid of typing the property name.
Assuming a property is unpopulated if it has the default value (i.e. Color == null and Size == 0):
var results = from marble in marbles
where (marble.Color == search.Color || search.Color == null)
&& (marble.Size == search.Size || search.Size == 0)
select marble;
You could override equals in your Marbles class
public override bool Equals(object obj)
{
var other = obj as Marble;
if (null == other) return false;
return other.Color == this.color && other.size == this.size; // (etc for your other porperties
}
and then you could search by
return marbles.Where(m => search == m);
Using reflection, this method will work on all types, regardless of how many or what type of properties they contain.
Will skip any properties that are not filled out (null for ref type, default value for value type). If it finds two properties that are filled out that do not match returns false. If all filled-out properties are equal returns true.
IsPartialMatch(object m1, object m2)
{
PropertyInfo[] properties = m1.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
object v1 = property.GetValue(m1, null);
object v2 = property.GetValue(m2, null);
object defaultValue = GetDefault(property.PropertyType);
if (v1.Equals(defaultValue) continue;
if (v2.Equals(defaultVAlue) continue;
if (!v1.Equals(v2)) return false;
}
return true;
}
To apply it to your example
public static IEnumerable<Marble> SearchMarbles(Marble search)
{
return marbles.Where(m => IsPartialMatch(m, search))
}
GetDefault() is method from this post, Programmatic equivalent of default(Type)
If you want to avoid targeting specific properties, you could use reflection. Start by defining a function that returns the default value of a type (see here for a simple solution, and here for something more elaborate).
Then, you can write a method on the Marble class, which takes an instance of Marble as a filter:
public bool MatchesSearch(Marble search) {
var t = typeof(Marble);
return !(
from prp in t.GetProperties()
//get the value from the search instance
let searchValue = prp.GetValue(search, null)
//check if the search value differs from the default
where searchValue != GetDefaultValue(prp.PropertyType) &&
//and if it differs from the current instance
searchValue != prp.GetValue(this, null)
select prp
).Any();
}
Then, the SearchMarbles becomes:
public static IEnumerable<Marble> SearchMarbles(Marble search) {
return
from marble in marbles
where marble.MatchesSearch(search)
select marble;
}
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.