8

我正在使用非常相似的循环来迭代任何传递对象的所有公共字段和属性。我确定字段/属性是否用特定的自定义属性装饰;如果是这样,则对字段或属性的值执行操作。两个循环是必要的,因为获取字段值的方法与获取属性值的方法不同。

// Iterate all public fields using reflection
foreach (FieldInfo fi in obj.GetType().GetFields())
{
  // Determine if decorated with MyAttribute.
  var attribs = fi.GetCustomAttributes(typeof(MyAttribute), true);
  if (attribs.Length == 1)
  {
    // Get value of field.
    object value = fi.GetValue(obj);
    DoAction(value);
  }
}

// Iterate all public properties using reflection
foreach (PropertyInfo pi in obj.GetType().GetProperties())
{
  // Determine if decorated with MyAttribute.
  var attribs = pi.GetCustomAttributes(typeof(MyAttribute), true);
  if (attribs.Length == 1)
  {
    // Get value of property.
    object value = pi.GetValue(obj, null);
    DoAction(value);
  }
}

我想将循环放在一个单一的通用方法中,以便我可以更简单地编写:

DoEachMember(obj.GetType().GetFields());
DoEachMember(obj.GetType().GetProperties());

这需要DoEachMember()接受MemberInfo类型(它是 和 的父类型FieldInfoPropertyInfo。问题是类中没有GetValue方法MemberInfo。两者都FieldInfo使用PropertyInfo不同的方法来获取字段/属性值:

public void DoEachMember(MemberInfo mi, object obj)
{
  foreach (MemberInfo mi in obj.GetType().GetProperties())
  {
    object value mi.GetValue(obj); // NO SUCH METHOD!
  }
}

因此,我声明了一个在循环中使用的委托,它接受 aMemberInfo并将该成员的值作为对象返回:

// Delegate to get value from field or property.
delegate object GetValue(MemberInfo mi, object obj);

问题

如何检测members[]数组中对象的类型,以便定义循环内使用的委托?目前,我正在使用数组的第一个元素members[0]. 这是一个好的设计吗?

public void DoEachMember(MemberInfo[] members, object obj)
{
  // Protect against empty array.
  if (members.Length == 0) return;

  GetValue getValue; // define delegate

  // First element is FieldInfo
  if (members[0] as FieldInfo != null)
    getValue = (mi, obj) => ((FieldInfo)mi).GetValue(obj);

  // First element is PropertyInfo
  else if (members[0] as PropertyInfo != null)
    getValue = (mi, obj) => ((PropertyInfo)mi).GetValue(obj, null);

  // Anything else is unacceptable
  else
    throw new ArgumentException("Must be field or property.");

  foreach (MemberInfo mi in members)
  {
    // Determine if decorated with MyAttribute.
    var attribs = mi.GetCustomAttributes(typeof(MyAttribute), true);
    if (attribs.Length == 1)
    {
      object value = getValue(mi, obj);
      DoStuff(value);
    }
  }
}

或者,我可以在每次迭代时检测到类型,但应该没有理由个别数组成员会有所不同:

foreach (MemberInfo mi in members)
{
  // ...

  object value;

  if ((var fi = mi as FieldInfo) != null)
    value = fi.GetValue(obj);

  else if ((var pi = mi as PropertyInfo) != null)
    value = pi.GetValue(obj, null);

  else
    throw new ArgumentException("Must be field or property.");

  DoStuff(value);
}
4

5 回答 5

2

您可以先投影到对象值,然后在循环中处理这些值。你的整个代码可以归结为这个(加上你的循环):

    /// <summary>
    /// Gets the value of all the fields or properties on an object that are decorated with the specified attribute
    /// </summary>
    private IEnumerable<object> GetValuesFromAttributedMembers<TAttribute>(object obj)
        where TAttribute : Attribute
    {
        var values1 = obj.GetType().GetFields()
                        .Where(fi => fi.GetCustomAttributes(typeof(TAttribute), true).Any())
                        .Select(fi => fi.GetValue(obj));
        var values2 = obj.GetType().GetProperties()
                        .Where(pi => pi.GetCustomAttributes(typeof(TAttribute), true).Any())
                        .Select(pi => pi.GetValue(obj, null));
        return values1.Concat(values2);
    }

您当前的代码混合了两个问题:查找值并对其进行处理。将这些问题分开会更清晰。上面的 LINQ 可以放在一个方法中,该方法从类中获取与给定属性匹配的字段或属性中的所有值,而另一种方法不仅仅是一个循环,对传递的任何内容进行工作。

不那么干净,但坚持你最初的目标,你可以这样做并传递一个适合你正在检索的 MemberInfo 类型的委托: -

    public void DoEachMember<TAttribute, TMembertype>(IEnumerable<TMembertype> members,
                             Func<TMembertype, object> valueGetter)
        where TMembertype : MemberInfo
    {
        foreach (var mi in members)
        {
            if (mi.GetCustomAttributes(typeof(TAttribute), true).Any())
            {
                // Get value of field.
                object value = valueGetter(mi);
                DoAction(value);
            }
        }
    }
于 2012-04-04T20:34:28.037 回答
1

您应该使用泛型:

public void DoEachMember<T>(T[] members, object obj) where T: MemberInfo
{
}

在里面,测试 T 是什么,并根据它决定调用哪个方法:

if(typeof(T)==PropertyInfo.GetType()) ...

您显然可以只进行一次检查,而不是每次迭代。

于 2012-04-04T20:03:29.327 回答
1

我通过将 MemberInfo 包装在这样的界面中来解决这个问题:

public interface IMemberInfo
{
    MemberInfo Wrapped { get; }

    object GetValue( object obj );

    void SetValue( object obj, object value );
}

internal abstract class MemberInfoWrapper : IMemberInfo
{
    protected readonly MemberInfo MemberInfo;

    public MemberInfoWrapper( MemberInfo memberInfo )
    {
        MemberInfo = memberInfo;
    }

    public abstract object GetValue( object obj );

    public abstract void SetValue( object obj, object value );

    public virtual MemberInfo Wrapped
    {
        get { return MemberInfo; }
    }
}

internal class PropertyInfoWrapper : MemberInfoWrapper
{
    public PropertyInfoWrapper( MemberInfo propertyInfo ) : base( propertyInfo )
    {
        Debug.Assert( propertyInfo is PropertyInfo );
    }

    public override object GetValue( object obj )
    {
        return ( (PropertyInfo)MemberInfo ).GetValue( obj, null );
    }

    public override void SetValue( object obj, object value )
    {
        ( (PropertyInfo)MemberInfo ).SetValue( obj, value, null );
    }
}

internal class FieldInfoWrapper : MemberInfoWrapper
{
    public FieldInfoWrapper( MemberInfo fieldInfo ) : base( fieldInfo )
    {
        Debug.Assert( fieldInfo is FieldInfo );
    }

    public override object GetValue( object obj )
    {
        return ( (FieldInfo)MemberInfo ).GetValue( obj );
    }

    public override void SetValue( object obj, object value )
    {
        ( (FieldInfo)MemberInfo ).SetValue( obj, value );
    }
}

还有一家工厂:

internal static class MemberInfoWrapperFactory
{
    public static IMemberInfo CreateWrapper( this MemberInfo memberInfo )
    {
        switch ( memberInfo.MemberType )
        {
            case MemberTypes.Property:
                return new PropertyInfoWrapper( memberInfo );
            case MemberTypes.Field:
                return new FieldInfoWrapper( memberInfo );
            default:
                return null;
        }
    }
}

鉴于此,您可以在您的方法中:

// Iterate all public members using reflection
foreach (MemberInfo mi in obj.GetType().GetMembers().Where(x => x is PropertyInfo || x is FieldInfo))
{
  // Determine if decorated with MyAttribute.
  var attribs = mi.GetCustomAttributes(typeof(MyAttribute), true);
  if (attribs.Length == 1)
  {
    // Get value of property.
    object value = mi.CreateWrapper().GetValue(obj, null);
    DoAction(value);
  }
}
于 2012-04-04T20:54:31.350 回答
0

尝试这个:

var areProperties = members.All(m => m is PropertyInfo);
var areFields = members.All(m => m is FieldInfo);

areProperties仅当members[]数组中的所有项目都是 PropertyInfo 对象时才为真。

于 2012-04-04T20:06:04.557 回答
0

你可以做这样的事情,如果你正在使用C# 4.0

public void DoEachMember(MemberInfo[] members, object obj)
{
   var properties = new List<dynamic>(); //dynamic objects list 
   properties.AddRange(members) ; // add all members to list of dynamics    
   foreach(dynamic d in porperties) //iterate over collection 
   {
      var attribs = d.GetCustomAttributes(typeof(MyAttribute), true); //call dynamicaly
      if (attribs.Length == 1)
      {
         // Get value of property.
         object value = d.GetValue(obj, null); //call dynamically
         DoAction(value);
      }
   }
 }

代码变得非常简洁明了。 应该工作。

祝你好运

于 2012-04-04T20:22:49.447 回答