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);
Related
I have a method
private static Dictionary<string, string> getRelationPropertyAttribute(Type type)
{
var dicRelation = new Dictionary<string, string>();
var properties = type.GetProperties();
foreach (var property in properties)
{
var attributes = property.GetCustomAttributes(inherit: false);
var customAttributes = attributes
.AsEnumerable()
.Where(a => a.GetType() == typeof(MongoDBFieldAttribute));
if (customAttributes.Count() <= 0)
continue;
for each (var attribute in custom attributes)
{
if (attribute is MongoDBFieldAttribute attr)
dicRelation[attr.Field] = property.Name;
}
}
return dicRelation;
}
In this typeof(MongoDBFieldAttribute) is getting me CustomAttributes list of all properties with
MOngoDBFieldAttribute type only and I have properties as :
[FieldIdentifier("SSI")]
[MongoDBField("Sender State ID")]
public string SenderStateID { get; set; }
[FieldIdentifier("SPH")]
[MongoDBField("Sender Phone")]
public string SenderPhone { get; set; }
How can I make the method generic so as to get Dictionary of MongoDBField or FieldIdentifier based on need?
There's already an (extension) method GetCustomAttributes<T>()
So you can write:
using System.Reflection;
var customAttributes = property.GetCustomAttributes<MongoDBFieldAttribute>();
foreach (var attribute in customAttributes)
{
dicRelation[attr.Field] = property.Name;
}
To make your method generic over the type of attribute, and return a dictionary where the keys are the attributes:
private static Dictionary<T, string> getRelationPropertyAttribute<T>(Type type) where T : Attribute
{
var dicRelation = new Dictionary<T, string>();
var properties = type.GetProperties();
foreach (var property in properties)
{
var customAttributes = property.GetCustomAttributes<T>();
foreach (var attribute in customAttributes)
{
dicRelation[attr] = property.Name;
}
}
return dicRelation;
}
Or you can use Linq to make it a bit terser:
private static Dictionary<T, string> getRelationPropertyAttribute<T>(Type type) where T : Attribute
{
var pairs = from property in type.GetProperties()
from attribute in property.GetCustomAttributes<T>()
select new KeyValuePair<T, string>(attribute, property.Name);
return new Dictionary<T, string>(pairs);
}
Recently I had to use ExpandoObject in my application, So I want to know how could I use my old Mapper to also map from Dynamic ExpandoOnjects, because some how it does not map the fields, and properties from an Expando.
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
public class Mapper
{
private static readonly Dictionary<KeyValuePair<Type, Type>, object> Maps = new Dictionary<KeyValuePair<Type, Type>, object>();
private static PropertyInfo[] _fromProperties;
private static PropertyInfo[] _toProperties;
private static FieldInfo[] _fromFields;
private static FieldInfo[] _toFields;
// Rules...
private static readonly Func<PropertyInfo, PropertyInfo, bool> MatchingProps = (t1, t2) => t1.Name == t2.Name && t1.PropertyType.Name == t2.PropertyType.Name;
private static readonly Func<FieldInfo, FieldInfo, bool> MatchingFields = (t1, t2) => t1.Name == t2.Name && t1.FieldType.Name == t2.FieldType.Name;
private static readonly Func<PropertyInfo, FieldInfo, bool> MatchingPropertyToField = (t1, t2) => t1.Name == t2.Name && t1.PropertyType.Name == t2.FieldType.Name;
private static readonly Func<FieldInfo, PropertyInfo, bool> MatchingFieldToProperty = (t1, t2) => t1.Name == t2.Name && t1.FieldType.Name == t2.PropertyType.Name;
public static void AddMap<TFrom, TTo>(Action<TFrom, TTo> map = null)
where TFrom : class
where TTo : class { Maps.Add(new KeyValuePair<Type, Type>(typeof(TFrom), typeof(TTo)), map); }
public static void Map<TFromType, TOType>(TFromType #from, TOType to)
{
var key = new KeyValuePair<Type, Type>(typeof(TFromType), typeof(TOType));
var map = (Action<TFromType, TOType>)Maps[key];
bool hasMapping = Maps.Any(x => x.Key.Equals(key));
if (!hasMapping)
throw new Exception(string.Format("No map defined for {0} => {1}", typeof(TFromType).Name, typeof(TOType).Name));
Type tFrom = typeof(TFromType);
Type tTo = typeof(TOType);
_fromProperties = tFrom.GetProperties();
_fromFields = tFrom.GetFields();
_toProperties = tTo.GetProperties();
_toFields = tTo.GetFields();
SyncProperties(#from, to);
SyncFields(#from, to);
if (!Equals(map, null))
map(#from, to);
}
private static void SyncProperties<TFromType, TOType>(TFromType objFrom, TOType objTo)
{
PropertyInfo[] fromProperties = _fromProperties;
PropertyInfo[] toProperties = _toProperties;
FieldInfo[] toFields = _toFields;
if (fromProperties != null && fromProperties.Any()) {
foreach (PropertyInfo fromProperty in fromProperties) {
if (toProperties.Any(x => x.Name == fromProperty.Name)) {
PropertyInfo destinationProperty = toProperties.FirstOrDefault(x => x.Name == fromProperty.Name);
if (MatchingProps(fromProperty, destinationProperty)) {
object val = fromProperty.GetValue(objFrom, null);
if (Equals(val, null)) continue;
if (!Equals(destinationProperty, null)) destinationProperty.SetValue(objTo, Convert.ChangeType(val, fromProperty.PropertyType), null);
}
}
if (toFields.Any(x => x.Name == fromProperty.Name)) {
FieldInfo destinationField = toFields.FirstOrDefault(x => x.Name == fromProperty.Name);
if (MatchingPropertyToField(fromProperty, destinationField)) {
object val = fromProperty.GetValue(objFrom, null);
if (Equals(val, null)) continue;
if (!Equals(destinationField, null)) destinationField.SetValue(objTo, val);
}
}
}
}
}
private static void SyncFields<TFromType, TOType>(TFromType objFrom, TOType objTo)
{
FieldInfo[] fromFields = _fromFields;
FieldInfo[] toFields = _toFields;
PropertyInfo[] toProperties = _toProperties;
if (fromFields != null && fromFields.Any()) {
foreach (FieldInfo fromField in fromFields) {
if (toFields.Any(x => x.Name == fromField.Name)) {
FieldInfo destinationField = toFields.FirstOrDefault(x => x.Name == fromField.Name);
if (MatchingFields(fromField, destinationField)) {
object val = fromField.GetValue(objFrom);
if (Equals(val, null)) continue;
if (!Equals(destinationField, null)) destinationField.SetValue(objTo, val);
}
}
if (toProperties.Any(x => x.Name == fromField.Name)) {
PropertyInfo destinationProperty = toProperties.FirstOrDefault(x => x.Name == fromField.Name);
if (MatchingFieldToProperty(fromField, destinationProperty)) {
object val = fromField.GetValue(objFrom);
if (Equals(val, null)) continue;
if (!Equals(destinationProperty, null)) destinationProperty.SetValue(objTo, val, null);
}
}
}
}
}
}
Usage:
static void Main()
{
dynamic o = new ExpandoObject();
o.Name = "Pouce";
o.Age = 42;
o.Rank = new Rank
{
Name = Ranks.Major
};
o.Guid = new Guid();
Soldier soldier = new Soldier();
Mapper.AddMap<ExpandoObject, Soldier>();
Mapper.Map(o, soldier);
Console.ReadLine();
}
public class Soldier
{
public string Name { get; set; }
public int Age { get; set; }
public Rank Rank { get; set; }
public Guid Guid { get; set; }
}
public class Rank
{
public Ranks Name { get; set; }
}
public enum Ranks
{
Private,
Specialist,
Corporal,
Sergeant,
Captain,
Major,
Colonel,
General
}
[Q] : How could I use my mapper (presented above) to map from the
dynamic ExpandoObject.
[P] : The problem is that it works perfectly in normal <object,
object> mapping; However when provided with <ExpandoObject, object>
it does not map anything.
This is because the properties of ExpandoObject are not real .NET properties. By using ExpandoObject with dynamic keyword you are able to give any arbitrary properties to the object and this is handled by the DLR at runtime.
You cannot use the regular static type's methods GetProperties() and GetFields() on the dynamic instance of ExpandoObject.
To extend your Mapper to consume ExpandObject you will have to consider it as a special case.
See my answer here it may help you.
EDIT: Reflection with ExpandoObject is not difficult. However, you wont get a set of PropertyInfo or FieldInfo from it. You just get KeyValuePair<string, object>. So, you may have to add an array of such KeyValuePair's to store the info.
In your Map() method, you can check for ExpandoObject as special case:
if (tFrom == typeof(ExpandoObject)) {
_fromExpandoProperties = #from.Select(kvp => kvp).ToArray();
// where _fromExpandoProperties is of type KeyValuePair<string, object>[]
} else {
_fromProperties = tFrom.GetProperties();
}
To get the name and value of the property you can then use .Key and .Value instead of .PropertyType.Name and .GetValue().
You will have to factor this specialization in all your code.
I'm new to lambda. So excuse me if my question is simple.
I have a method that uses reflection to set a property on some types:
public void WriteId(object obj, int id) {
var type = obj.GetType();
var prop = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead && p.CanWrite)
.Where(p => p.Name == "Id")
.Where(p.PropertyType == typeof(int))
.FirstOrDefault();
if(prop != null)
prop.SetValue(obj, id, null);
}
Can you show me please how can I create a lambda that do same job? Actually I want to create a lambda for each type, compile it, and cache it. Thanks in advance.
I would install FastMember from NuGet, and then use:
var wrapped = ObjectAccessor.Create(obj);
obj["Id"] = id;
which does pretty much what you say, except it happens to use ILGenerator via TypeBuilder (rather than Expression) - but all the caching etc is there.
A second cheaky approach is to get dynamic to do it all for you:
((dynamic)obj).Id = id;
But if you want to use Expression for other reasons:
using System;
using System.Linq.Expressions;
static class Program
{
static void Main()
{
var obj = new Foo { Id = 2 };
WriteId(obj, 6);
Console.WriteLine(obj.Id); // 6
}
private static class SneakyCache<T>
{
public static readonly Action<T, int> SetId;
static SneakyCache()
{
var obj = Expression.Parameter(typeof(T), "obj");
var id = Expression.Parameter(typeof(int), "id");
SetId = Expression.Lambda<Action<T, int>>(
Expression.Assign(Expression.Property(obj, "Id"), id),
obj, id).Compile();
}
}
public static void WriteId<T>(T obj, int id) where T : class
{
SneakyCache<T>.SetId(obj, id);
}
}
class Foo
{
public int Id { get; set; }
}
I have refactored out a generic CSV builder for List using lambda expressions to access the correct values
public static string ToCsv<TModel>(this List<TModel> list, string delimiter, string lineBreak, string valueWrap, params Expression<Func<TModel, object>>[] expressions)
{
var sb = new StringBuilder();
var headers = expressions.Select(m => String.Format("{0}{1}{0}", valueWrap, GetPropertyName(m))).ToArray();
sb.Append(String.Format("{0}{1}", String.Join(delimiter, headers), lineBreak));
foreach (var listItem in list)
{
var values = expressions.Select(m => String.Format("{0}{1}{0}", valueWrap, m.Compile()(listItem))).ToArray();
sb.Append(String.Format("{0}{1}", String.Join(delimiter, values), lineBreak));
}
return sb.ToString();
}
This works well, however because I am trying to move this into some common code for several projects across several servers to access. I cannot reference the System.Web.Mvc assembly
Is there a good way to access the DisplayName Attribute and if that doesnot exist, access the variable name?
My current attempt access the ((MemberExpression) expression.Body).Member.Name however it will not work if it has to convert a value to string. (ie passing an int and implicitly converting)
A second attempt of iterating over the attributes did not work for inner class (ie model => model.innerClass.property)
With a couple simple helper functions, you can eliminate all reference to the System.Web.Mvc assembly. You'll also notice in the example below that (model => model.member.property) works.
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Linq.Expressions;
using System.ComponentModel;
using System.Reflection;
namespace Test
{
public class Program
{
public static void Main(string[] args)
{
List<Class1> foobars = new List<Class1>();
foobars.Add(new Class1 { Foo = "Hello world!", Bar = -1, Skip = false, Ref = new Class2 { ToBe = true } });
string result = foobars.ToCsv(",", Environment.NewLine, "\"", m => m.Foo, m => m.Bar, m => m.Ref.ToBe);
}
public class Class1
{
[DisplayName("Foo Property")]
public string Foo { get; set; }
public int Bar { get; set; }
[DisplayName("Skipped Property")]
public bool Skip { get; set; }
[DisplayName("Reference")]
public Class2 Ref { get; set; }
}
public class Class2
{
[DisplayName("To Be or Not To Be")]
public bool ToBe { get; set; }
}
}
public static class Extensions
{
public static string ToCsv<TModel>(this List<TModel> list, string delimiter, string lineBreak, string valueWrap, params Expression<Func<TModel, object>>[] expressions)
{
var sb = new StringBuilder();
var headers = expressions.Select(m => String.Format("{0}{1}{0}", valueWrap, GetDisplayName(m))).ToArray();
sb.Append(String.Format("{0}{1}", String.Join(delimiter, headers), lineBreak));
foreach (var listItem in list)
{
var values = expressions.Select(m => String.Format("{0}{1}{0}", valueWrap, m.Compile()(listItem))).ToArray();
sb.Append(String.Format("{0}{1}", String.Join(delimiter, values), lineBreak));
}
return sb.ToString();
}
// Get DisplayName, otherwise fallback to Name
private static string GetDisplayName(LambdaExpression memberReference)
{
MemberInfo info = GetMemberInfo(memberReference);
DisplayNameAttribute displayNameAttr = Attribute.GetCustomAttribute(info, typeof(DisplayNameAttribute)) as DisplayNameAttribute;
return (displayNameAttr != null ? displayNameAttr.DisplayName : info.Name);
}
// Can be swapped for your favourite GetMemberInfo/GetPropertyInfo utility method (there are many out there)
// Source: http://blog.baltrinic.com/software-development/dotnet/extension-methods-for-converting-lambda-expression-to-strings
private static MemberInfo GetMemberInfo(LambdaExpression memberReference)
{
MemberExpression memberExpression;
var unary = memberReference.Body as UnaryExpression;
if (unary != null)
//In this case the return type of the property was not object,
//so .Net wrapped the expression inside of a unary Convert()
//expression that casts it to type object. In this case, the
//Operand of the Convert expression has the original expression.
memberExpression = unary.Operand as MemberExpression;
else
//when the property is of type object the body itself is the
//correct expression
memberExpression = memberReference.Body as MemberExpression;
if (memberExpression == null || !(memberExpression.Member is MemberInfo))
throw new ArgumentException("Expression was not of the form 'x => x.member'.");
return memberExpression.Member;
}
}
}
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.