I was wanting something similar to this for something I'm working on, and I wanted a better generic way to handle it.
I tested a lot of ways but I settled on making a generic function that could be used to get any type of member, and then just calling it for properties and fields to get the value members.
Getting the Members
public static MemberInfo[] GetValueMembers(this Type t, BindingFlags bindings)
{
MemberInfo[] result = t.GetMembersSlow(bindings, MemberTypes.Field | MemberTypes.Property);
return result;
}
public static MemberInfo[] GetMemberSlow(
this Type t, BindingFlags bindings, MemberTypes types
){
MemberInfo[] result = t.FindMembers(types, bindings, (o, p) => true, null);
return result;
}
This offers okay performance, not stellar, but better than some other ways I tried. The IEnumerable<MemberInfo>.Concat()
approach of casting results of GetProperties()
and GetMethods()
was particularly slow comparatively.
But, I got really good performance by doing GetProperties()
and GetFields()
and using Array.Copy
, so I decided to see if I could write something that uses all the GetX()
types from Type based on what you request and then Array.Copy()
all the results together.
public static MemberInfo[] GetMembers(this Type t, BindingFlags bindings, MemberTypes types)
{
List<MemberInfo[]> resultArrays = new List<MemberInfo[]>();
MemberTypes[] memberTypes = types.Split();
MemberInfo[] buffer = null;
int totalLength = 0;
foreach (MemberTypes memberType in memberTypes)
{
switch (memberType)
{
case MemberTypes.Constructor:
buffer = t.GetConstructors(bindings);
break;
case MemberTypes.Event:
buffer = t.GetEvents(bindings);
break;
case MemberTypes.Field:
buffer = t.GetFields(bindings);
break;
case MemberTypes.Method:
buffer = t.GetMethods(bindings);
break;
case MemberTypes.Property:
buffer = t.GetProperties(bindings);
break;
default:
// maybe do something here? the other values don't really
// make sense in a GetMembers call, probably ignore them.
break;
}
if (buffer != null && buffer.Length > 0)
{
resultArrays.Add(buffer);
totalLength += buffer.Length;
buffer = null;
}
}
MemberInfo[] result = new MemberInfo[totalLength];
int currentIndex = 0;
foreach (MemberInfo[] resultArray in resultArrays)
{
if (resultArray.Length == 0)
continue;
Array.Copy(resultArray, 0, result, currentIndex, resultArray.Length);
currentIndex += resultArray.Length;
}
return result;
}
There's a bit hidden here, in the types.Split()
call, that's something I wrote that takes a flags enum and spits out an array of all the individual flags that were contained in it so it's easy to loop over. See https://stackoverflow.com/a/72251903/832859 for the Split()
code.
Oh, this is kind of important, and I didn't get it at first: MemberInfo
is an abstract base type, and all the XInfo
types are derived from it, and easily castable to and from. So to change, say, a ConstructorInfo[]
to a MemberInfo[]
, just assign it to a MemberInfo[]
variable, or cast it with (MemberInfo[])
or X as MemberInfo[]
, etc.
Depending on what's passed in, this function will grab all the relevant MemberInfo
arrays from the Type based on the bindings/membertypes you've passed in, grabs them individually from the specific functions, and then copies them all into one big MemberInfo[]
. As I mentioned I got good performance on the Properties+Fields, but I was worried extra overhead from so many of these would slow it down.
Turns out my worries were unfounded, it outperformed everything else I tried, by a pretty good margin. This code is all marginally tested out and working. I tested the two remaining GetMembers()
on Forms.GetType()
and it matches what's returned by Type.FindMembers()
.
Using the members
Lastly, I wrote some helpers for actually making use of the Properties+Fields data. Since MemberInfo
is abstract, there's no convenient way to get values from instances of the members.
When you have a FieldInfo
or PropertyInfo
that you want to get information from, or values from its instances, you just create a MemberFactory, passing in the field or property info as a MemberInfo
.
If you need the type of the member, you call GetMemberSystemType()
which returns the System.Type
of the member.
NB: This probably doesn't handle all cases by a long shot, but it works for just basic members.
public struct MemberFactory
{
public readonly FieldInfo fieldInfo;
public readonly PropertyInfo propertyInfo;
private MemberFactory(MemberInfo memberInfo)
{
fieldInfo = memberInfo as FieldInfo;
propertyInfo = memberInfo as PropertyInfo;
if (fieldInfo == null && propertyInfo == null)
throw new ArgumentException("memberInfo must derive from FieldInfo or PropertyInfo", memberInfo.Name);
}
public Type GetMemberSystemType()
{
Type result = null;
if (IsProperty)
result = propertyInfo.PropertyType;
else if (IsField)
result = fieldInfo.FieldType;
return result;
}
public bool IsProperty { get => propertyInfo != null; }
public bool IsField { get => fieldInfo != null; }
public Member MakeMember(object instance)
{
return new Member(instance, this);
}
public static MemberFactory Create(MemberInfo memberInfo)
{
return new MemberFactory(memberInfo);
}
}
Then, when you have an actual instance you want to deal with, just call MakeMember()
with the instance to get a Member
object to act on that instance.
Since we've got both a property and a field to have to check, in the implementation, I just used the null conditional/null coalescing to check them both. I didn't test whether there was much of a performance hit over just checking IsMethod()
/IsProperty()
on the MemberFactory
but I think it's fine.
And finally, a couple of extensions functions so that if you have a MemberInfo
and an instance, you don't have to create a MemberFactory
/Member
to use them, in case you just just need to access a value quickly without a lot of set up.
public class Member
{
object instance;
MemberFactory factory;
public Member(object instance, MemberFactory factory)
{
this.instance = instance;
this.factory = factory;
}
public object GetValue()
{
return
factory.fieldInfo?.GetValue(instance)
?? factory.propertyInfo?.GetValue(instance);
}
public void SetValue(object value)
{
factory.fieldInfo?.SetValue(instance, value);
factory.propertyInfo?.SetValue(instance, value);
}
}
public static void SetValue(this MemberInfo memberInfo, object instance, object value)
{
MemberFactory.Create(memberInfo).MakeMember(instance).SetValue(value);
}
public static object GetValue(this MemberInfo memberInfo, object instance)
{
return MemberFactory.Create(memberInfo).MakeMember(instance).GetValue();
}
So, that's about it. I just was needing this for something else and this was one of the pages I came across when I was searching for help, so I noted it and came back once I had some sort of solid semi-tested ideas worked out.
The kernel of an idea that made me work on this was pretty much just what's in the Member.SetValue()
call.