C # Iterate an array of a parent type to call a non-polymorphic method on a derived type

I use very similar loops to iterate through all open fields and properties of any passed object. I determine if a field / property is decorated with a specific user attribute; if so, an action is taken on the value of the field or property. Two loops are needed because the method of getting the field value is different from the method to get the value of the property.

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

I would like to put the loop in one general method so that I can write instead, easier:

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

This requires DoEachMember() to accept the MemberInfo type (which is the parent type of both FieldInfo and PropertyInfo ). The problem is that there is no GetValue method in the MemberInfo class. Both FieldInfo and PropertyInfo use different methods to get the field / property value:

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

Thus, I declare the delegate to use inside the loop, which takes MemberInfo and returns the value of this element as an object:

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

Question

How to determine the type of objects in the members[] array to determine the delegate used inside the loop? I am currently using the first element of the array, members[0] . Is this a good design?

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

Alternatively, I could determine the type at each iteration, but there should be no reason why the individual members of the array will ever differ:

 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); } 
+8
c #
source share
5 answers

You can first project the values ​​of objects, and then work with them in your own loop. All your code can be reduced to this (plus your loop):

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

Your current code mixes two problems: finding values ​​and doing something with them. It would be easier to separate these issues. The aforementioned LINQ can be placed in one method that extracts all the values ​​from the class that are in the fields or properties that correspond to this attribute, and the other is just a loop that does the work of passing it on.

Not as clean, but adhering to its original purpose, you can do this and pass a delegate that matches the type of MemberInfo you return: -

  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); } } } 
+2
source share

You should use generics:

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

Inside, check what T is and decide which method to call based on this:

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

Obviously, you can only check once, not every iteration.

+1
source share

I approached this by wrapping MemberInfo in this interface:

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

And factory:

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

Given this, you can in your method:

 // 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); } } 
+1
source share

Try the following:

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

areProperties will be true only if all members of the members[] array are PropertyInfo objects.

0
source share

You can do something similar if using 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); } } } 

The code becomes very short and simple. It should work.

Good luck.

0
source share

All Articles