How can I filter FieldInfos that are the underlying implementation of a class event?
I want to get all the fields of a class without getting the underlying implementations of the class e开发者_如何学Pythonvent. type.GetFields(BindingFlags...) returns the nuderlying delegate for event fields. Does anyone knows how to filter them out ?
Events in .NET generate a field with the same and type as the event. In addition they generates two methods (adder and remover, which have the same name as the field with prefixes 'add_' and 'remove_').
In order to filter event backing fields you can remove fields with same name as events. You can be sure no field will be defined with same name as event since the compiler will fail the compilation if another member is defined with the same name.
For example:
public IEnumerable<FieldInfo> FilterBackingEventFields(Type type)
{
List<string> eventNames = type
.GetEvents().Select(eventInfo => eventInfo.Name).ToList();
FieldInfo[] fieldInfos = type
.GetFields(BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.Instance);
return fieldInfos.Where(fieldInfo => !eventNames.Contains(fieldInfo.Name));
}
Usage example:
public class ClassWithEventAndField
{
public event EventHandler MyEvent;
public int MyField;
}
[Test]
public void TestFieldsFilter()
{
IEnumerable<FieldInfo> fields =
FilterBackingEventFields(typeof(ClassWithEventAndField));
FieldInfo expectedField = typeof(ClassWithEventAndField).GetField("MyField");
Assert.That(fields, Is.EquivalentTo(new[] { expectedField }));
}
EDIT: added support to work with VB and C#
This code will work on auto generated events (custom adder or remover will break the code). This is also a risky code, it makes some assumptions on the way adder method is generated and compiles. I am posting this code as "Academic" information, I wouldn't use it in production code.
public IEnumerable<FieldInfo> FilterBackingEventFields(Type type)
{
List<int> backingFieldsTokens = type
.GetEvents().Select(eventInfo => MetadataToken(eventInfo)).ToList();
FieldInfo[] fieldInfos = type
.GetFields(BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.Instance);
return fieldInfos
.Where(fieldInfo => !backingFieldsTokens.Contains(fieldInfo.MetadataToken));
}
private static int MetadataToken(EventInfo eventInfo)
{
MethodInfo adderMethod = eventInfo.GetAddMethod();
int fieldToken =
adderMethod.GetMethodBody().GetILAsByteArray()[3] |
adderMethod.GetMethodBody().GetILAsByteArray()[4] << 8 |
adderMethod.GetMethodBody().GetILAsByteArray()[5] << 16 |
adderMethod.GetMethodBody().GetILAsByteArray()[6] << 24;
return fieldToken;
}
The assumption made here is that the bytes 3-6 in the adder method body are the token of the backing field of the event. I really hope someone will post an elegant and safe solution to this problem :)
精彩评论