2013-11-20 3 views
0

У меня есть класс с несколькими свойствами, у меня есть настраиваемая настройка атрибута, одна для TextField и одна для ValueField, я использую IEnumerable, поэтому не могу просто выбрать поля, которые я хочу, мне нужно в принципе:Оператор LINQ, выберите поле с определенным атрибутом

collectionItems.ToDictionary(o => o.FieldWithAttribute<TextField>, o => o.FieldWithAttribute<ValueField>); 

Надеюсь, вы получите то, что я пытаюсь сделать, это не нужно использовать дженерики, как указано выше, мне просто нужно сделать что-то подобное, чтобы получить отмеченные поля от большого объекта, поэтому у меня может быть симпатичный маленький словарь с парами ключевых значений.

Пример класса, который является TEntity:

public class Product 
{ 
    [TextField] 
    public string ProductTitle { get; set; } 
    [ValueField] 
    public int StyleID { get; set; } 
    //Other fields... 
} 

Любые идеи, как я могу добиться этого? Возможно, используя рефлексию как-то в заявлении LINQ?

+0

Я думаю, что вы можете использовать какой-то интерфейс или рассмотреть возможность использования словаря в классе для текста и полей значений. Если у вас есть только один ключ и одно значение для каждого продукта, есть ли какая-то причина, которая не может быть интерфейсом? – Magus

ответ

1

Если вы собираетесь использовать отражение, вы должны, вероятно, кэшируйте элементы доступа для участников, чтобы каждый раз ударять каждый раз при отражении на каждом элементе. Вы могли бы сделать что-то вроде этого:

// 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; 
    } 
} 

Это до вас, как вы хотите иметь дело с предметами, чьи типы не имеют желаемые приписываемые член. Я решил вернуть null.

Пример использования:

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]); 
+0

Все ответы были фантастическими, но это, в частности, добавляет кеширование и рассматривает объекты, которые не имеют атрибута более подробно, спасибо! –

1
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; 
} 

что-то вроде этого

1

Это должно сделать трюк

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); 
} 

и при использовании его

var dictionary = products.ToDictionary(x => x.FieldWithAttribute<TextFieldAttribute, string>(), 
       x => x.FieldWithAttribute<ValueFieldAttribute, int>()); 
Смежные вопросы