LINQ语句,选择具有特定属性的字段

时间:2013-11-20 17:55:09

标签: c# linq generics reflection

我有一个包含一些属性的类,我有一个自定义属性设置,一个用于TextField,一个用于ValueField,我使用IEnumerable,所以不能只选择我想要的字段,我基本上需要:

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语句中以某种方式使用反射?

3 个答案:

答案 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 :(得分: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>());

答案 2 :(得分: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]);