【问题标题】:LINQ statement, select field that has specific attributeLINQ 语句,选择具有特定属性的字段
【发布时间】:2013-11-20 17:55:09
【问题描述】:

我有一个有几个属性的类,我有一个自定义属性设置,一个用于 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 语句中以某种方式使用反射?

【问题讨论】:

  • 我认为您可能想要使用某种接口或考虑在类中为文本和值字段使用字典。如果每个产品只有一个键和一个值,是不是有什么原因不能成为接口?

标签: c# linq generics reflection


【解决方案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]);

【讨论】:

  • 所有答案都很棒,但这个特别添加了缓存并更深入地考虑没有属性的对象,谢谢!
【解决方案2】:
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;
}

类似的东西

【讨论】:

    【解决方案3】:

    这应该可以解决问题

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-15
      • 1970-01-01
      • 2012-06-08
      • 1970-01-01
      • 2017-04-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多