8

渡されたオブジェクトのすべてのパブリックフィールドとプロパティを反復処理するために、非常によく似たループを使用しています。フィールド/プロパティが特定のカスタム属性で装飾されているかどうかを判断します。その場合、フィールドまたはプロパティの値に対してアクションが実行されます。フィールド値を取得する方法はプロパティ値を取得する方法とは異なるため、2つのループが必要です。

// 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()を受け入れる必要があります。問題は、クラスにメソッドがないことです。両方とも、異なるメソッドを使用してフィールド/プロパティ値を取得します。MemberInfoFieldInfoPropertyInfoGetValueMemberInfoFieldInfoPropertyInfo

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

MemberInfoしたがって、を取り、そのメンバーの値をオブジェクトとして返すループ内で利用するデリゲートを宣言します。

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

現在のコードには、値を見つけることとそれらを使って何かをすることの2つの懸念が混在しています。これらの懸念を分離する方がよりクリーンです。上記のLINQは、特定の属性に一致するフィールドまたはプロパティにあるクラスからすべての値をフェッチする1つのメソッドと、渡されたものに対して作業を行う単なるループではない別のメソッドに配置できます。

それほどクリーンではありませんが、元の目標に固執することで、これを実行し、取得する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

あなたが使用している場合、あなたはこのようなことをすることができます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 に答える
0

これを試して:

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

arePropertiesmembers[]配列内のすべてのアイテムがPropertyInfoオブジェクトである場合にのみtrueになります。

于 2012-04-04T20:06:04.557 に答える