Reputation: 1005
I have a class with a few properties, I've got a custom attribute setup, one for TextField and one for ValueField, I am using an IEnumerable, so can't just select the fields I want, I need to basically:
collectionItems.ToDictionary(o => o.FieldWithAttribute<TextField>, o => o.FieldWithAttribute<ValueField>);
Hopefully you get what I am trying to do, this doesn't need to use generics as above, I just need to do something similar to get the marked fields from a large object, so I can have a nice little dictionary of key value pairs.
Example class that is the TEntity:
public class Product
{
[TextField]
public string ProductTitle { get; set; }
[ValueField]
public int StyleID { get; set; }
//Other fields...
}
Any ideas how I can achieve this? Perhaps using reflection somehow in the LINQ statement?
Upvotes: 0
Views: 891
Reputation: 25623
If you're going to use reflection, you should probably cache the member accessors to avoid the performance hit of reflecting on every item, every time. You could do something like this:
// Type aliases used for brevity
using Accessor = System.Func<object, object>;
using E = System.Linq.Expressions.Expression;
internal static class AttributeHelpers
{
private const BindingFlags DeclaredFlags = BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.DeclaredOnly;
private const BindingFlags InheritedFlags = BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic;
private static readonly Accessor NullCallback = _ => null;
[ThreadStatic]
private static Dictionary<Type, Dictionary<Type, Accessor>> _cache;
private static Dictionary<Type, Accessor> GetCache<TAttribute>()
where TAttribute : Attribute
{
if (_cache == null)
_cache = new Dictionary<Type, Dictionary<Type, Accessor>>();
Dictionary<Type, Accessor> cache;
if (_cache.TryGetValue(typeof(TAttribute), out cache))
return cache;
cache = new Dictionary<Type, Accessor>();
_cache[typeof(TAttribute)] = cache;
return cache;
}
public static object MemberWithAttribute<TAttribute>(this object target)
where TAttribute : Attribute
{
if (target == null)
return null;
var accessor = GetAccessor<TAttribute>(target.GetType());
if (accessor != null)
return accessor(target);
return null;
}
private static Accessor GetAccessor<TAttribute>(Type targetType)
where TAttribute : Attribute
{
Accessor accessor;
var cache = GetCache<TAttribute>();
if (cache.TryGetValue(targetType, out accessor))
return accessor;
var member = FindMember<TAttribute>(targetType);
if (member == null)
{
cache[targetType] = NullCallback;
return NullCallback;
}
var targetParameter = E.Parameter(typeof(object), "target");
var accessorExpression = E.Lambda<Accessor>(
E.Convert(
E.MakeMemberAccess(
E.Convert(targetParameter, targetType),
member),
typeof(object)),
targetParameter);
accessor = accessorExpression.Compile();
cache[targetType] = accessor;
return accessor;
}
private static MemberInfo FindMember<TAttribute>(Type targetType)
where TAttribute : Attribute
{
foreach (var property in targetType.GetProperties(DeclaredFlags))
{
var attribute = property.GetCustomAttribute<TAttribute>();
if (attribute != null)
return property;
}
foreach (var field in targetType.GetFields(DeclaredFlags))
{
var attribute = field.GetCustomAttribute<TAttribute>();
if (attribute != null)
return field;
}
foreach (var property in targetType.GetProperties(InheritedFlags))
{
var attribute = property.GetCustomAttribute<TAttribute>();
if (attribute != null)
return property;
}
foreach (var field in targetType.GetFields(InheritedFlags))
{
var attribute = field.GetCustomAttribute<TAttribute>();
if (attribute != null)
return field;
}
return null;
}
}
It's up to you how you want to deal with items whose types lack the desired attributed members. I chose to return null
.
Example usage:
var lookup = Enumerable
.Range(1, 20)
.Select(i => new Product { Title = "Product " + i, StyleID = i })
.Select(
o => new
{
Text = o.MemberWithAttribute<TextFieldAttribute>(),
Value = o.MemberWithAttribute<ValueFieldAttribute>()
})
.Where(o => o.Text != null && o.Value != null)
.ToDictionary(o => o.Text, o => o.Value);
foreach (var key in lookup.Keys)
Console.WriteLine("{0}: {1}", key, lookup[key]);
Upvotes: 1
Reputation: 2806
This should do the trick
public static TRet FieldWithAttribute<TAttr, TRet>(this object obj) where TAttr : Attribute
{
var field = obj.GetType()
.GetProperties()
.SingleOrDefault(x => Attribute.IsDefined(x, typeof (TAttr)));
return field == null ? default(TRet) : (TRet)field.GetValue(obj);
}
and when you use it
var dictionary = products.ToDictionary(x => x.FieldWithAttribute<TextFieldAttribute, string>(),
x => x.FieldWithAttribute<ValueFieldAttribute, int>());
Upvotes: 1
Reputation: 2524
public static object FieldWithAttribute<T>(this object obj)
{
var field = obj.GetType()
.GetProperties()
.SIngleOrDefault(x => x.CustomAattributes.Any(y => y.AttributeType == typeof(T));
return field != null ? field.GetValue(obj) : null;
}
something like this
Upvotes: 1