views:

29

answers:

1

I want to get all the fields of a class without getting the underlying implementations of the class event. type.GetFields(BindingFlags...) returns the nuderlying delegate for event fields. Does anyone knows how to filter them out ?

A: 

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 :)

Elisha
I hoped for some better support from reflection API. I know I can do it like this but then I will have to implement it differently for VB (the underlying field for event called X is XEvent).
Izik Shmulewitz
Elisha