7

This question deals with events (base class events and subclass events) and event handlers. I'm working on existing code, that doesn't seem to work the way the author expected it. I have difficulty understanding why it doesn't work though, so I want to understand what's going on before I try to fix the existing code.

I've found the following question, which may or may not suggest I need to make an additional event handler for the subtype events:

C#: Raising an inherited event

If making an additional event handler is indeed the solution, I would still like to learn why this is the case. This is my first question here, and I did really try to search for the answer/explanation to my question, but sincere apologies if it's still something I should've easily found. A stern "RTFM!" with a educational link would be fine with me at this point :)

We have 2 event classes, a base type and a subtype. The subtype event exists to deal with deletion events.

public class BaseTypeEvent
{
    public Guid Id { get; set; }
    public string Name { get; set; }

    public BaseTypeEvent()
    { }

    public BaseTypeEvent(SomeRandomThing item)
    {
        Id = item.Id;
        Name = item.Name;
    }
}

public class SubTypeEvent : BaseTypeEvent
{
    public DateTimeOffset Deleted { get; set; }

    public SubTypeEvent()
    {
        Deleted = DateTimeOffset.UtcNow;
    }
}

A usage of these events that seems to be failing:

public class UsageClass
{   
    public UsageClass(IEventBusService eventBusService)
    {
        eventBusService.MyBaseTypeEvents += HandleMethod;
    }

    private void HandleMethod(BaseTypeEvent e)
    {
        if(e is SubTypeEvent)
        {
            //code that deals with deletion events
            //execution never actually gets here
        }

        //code that deals with events that are not deletion events
     }
}

The declaration of the events are in the IEventBusService and EventBusService:

public delegate void MyEventHandler(BaseTypeEvent e);

public interface IEventBusService
{
    public event MyEventHandler MyBaseTypeEvents;

    void PublishStuff(BaseTypeEvent e);
}

public class EventBusService : IEventBusService, IDisposable 
{
    public void Initialize()
    {
        //Bus is MassTransit
        Bus.Initialize(sbc =>
            {
                sbc.Subscribe(subs => subs.Handler<BaseTypeEvent>(OnBaseTypeEvent));
            }
    }

    private void OnBaseTypeEvent(BaseTypeEvent e)
    {
        if (MyBaseTypeEvents == null) return;
        try
        {
            MyBaseTypeEvents(e);
        }
        catch (Exception e)
        {
            //some logging
        }
    }

    public event MyEventHandler MyBaseTypeEvents;

    public void PublishStuff(BaseTypeEvent e)
    {
        //some logging

        //publish e to the event bus of our choice (MassTransit)
        Bus.Instance.Publish(e);
    }
}

And then finally the place where we send the deletion event (to try to delete an item of what I have cleverly named SomeRandomThing above):

eventBusService.PublishStuff(new SubTypeEvent
    {
        Id = id,
        Deleted = DateTimeOffset.UtcNow
    });

So the problem: after sending the deletion event with the last line of code above, the if-statement in the UsageClass that checks whether an incoming event is of type SubTypeEvent is never actually true. The type of e in HandleMethod of UsageClass is BaseTypeEvent.

Edit:

I've decided to get rid of the subtyping in this case. We now no longer have BaseTypeEvent and SubTypeEvent, but simply EventTypeA and EventTypeB. One deals with creates and updates, the other deals with deletes (for which we need significantly less information that the creates and updates anyway).

public delegate void MyEventAHandler(EventTypeA e);
public delegate void MyEventBHandler(EventTypeB e);

and

void PublishStuffForA(EventTypeA e);
void PublishStuffForB(EventTypeB e);

and so on.

I've made an extra subscription to MassTransit in the Initialize method of our EventbusService, and made extra handlers in the various UsageClasses that needed them:

sbc.Subscribe(subs => subs.Handler<EventTypeA>(OnEventTypeA));
sbc.Subscribe(subs => subs.Handler<EventTypeB>(OnEventTypeB));

and

public UsageClass(IEventBusService eventBusService)
{
    eventBusService.MyEventTypeAEvents += HandleMethodForA;
    eventBusService.MyEventTypeBEvents += HandleMethodForB;
}

and so on.

I now no longer have to check if an incoming event is of a certain type, I just handle to two types separately. Perhaps a cop out, but it works.

I'm hesitant to qualify this as the answer to my own question, as @Glubus' comments as well as @Travis' comments were what answered my question. Still thought this small edit write-up might be nice to let everyone know what I did as a solution :)

Edit 2:

Sources of information that were helpful:

Derived types are not published to consumers in MassTransit

MassTransit message mis-typing

MassTransit: Message contracts, polymorphism and dynamic proxy objects

https://groups.google.com/forum/#!searchin/masstransit-discuss/polymorphism/masstransit-discuss/q_M4erHQ7OI/FxaotfIzI7YJ

Community
  • 1
  • 1
  • So of what type is the event then? – Glubus Jun 16 '16 at 10:10
  • @Glubus: BaseTypeEvent – user6473667 Jun 16 '16 at 10:11
  • Seems to me that at that point the object is indeed a BaseTypeEvent, though it should be castable to a SubTypeEvent, what happens if you try to execute this `e as SubTypeEvent` in HandleMethod? – Glubus Jun 16 '16 at 10:18
  • `//publish e to the event bus of our choice` what does this mean? A call to `OnBaseTypeEvent`? – Matteo Umili Jun 16 '16 at 10:19
  • @Glubus: doesn't work, e as SubTypeEvent will return null :( – user6473667 Jun 16 '16 at 10:20
  • @codroipo: apologies for leaving it out, added it in now. The publish e to the event bus of our choice does the following: Bus.Instance.Publish(e); – user6473667 Jun 16 '16 at 10:22
  • Reffer to this answer, and trying doing a reverse: http://stackoverflow.com/a/755022/1782777 – Jurijs Kastanovs Jun 16 '16 at 10:23
  • @user6473667 Well, now my question is: What is `Bus`? :) – Matteo Umili Jun 16 '16 at 10:25
  • Or rather this. It has an extensive answer: http://stackoverflow.com/questions/2742276/how-do-i-check-if-a-type-is-a-subtype-or-the-type-of-an-object – Jurijs Kastanovs Jun 16 '16 at 10:27
  • @codroipo ha, good question! :) apologies again, it is MassTransit. – user6473667 Jun 16 '16 at 10:28
  • @codroipo The bus pattern allows different components to Publish and Subscribe to certain messages. It's an old and frankly discouraged pattern: https://msdn.microsoft.com/en-us/library/ff649664.aspx – Glubus Jun 16 '16 at 10:30
  • @user6473667 Can you show how you implemented the Subscribe and Publish? You seem to lose the type information, which you might accidentally do in the publishing and subscribing of the communication. – Glubus Jun 16 '16 at 10:32
  • @Glubus: I added it to the code above as well, the Subscribe is a call to the MassTransit bus as follows: sbc.Subscribe(subs => subs.Handler(OnBaseTypeEvent)); where sbc is the ServiceBusConfigurator (code above is hopefully clearer than this comment) – user6473667 Jun 16 '16 at 10:49
  • @JurijsKastanovs: both instanceCheck and assignmentCheck are false here: `Type hopefullySubType = typeof(SubTypeEvent); var instanceCheck = hopefullySubType.IsInstanceOfType(a); var assignmentCheck = hopefullySubType.IsAssignableFrom(a.GetType());` – user6473667 Jun 16 '16 at 11:00
  • 2
    You pass the `BaseTypeEvent` type to the subscribe method, meaning that any object that will be sent to the subscriber will be casted to that type. Try changing your class definition to `EventBusService where T : BaseTypeEvent`, and subscribing to `T` instead of `BaseTypeEvent` You can also type parameterize the Initialize method instead of your class. – Glubus Jun 16 '16 at 11:07
  • @Glubus: thanks for that tip! You are probably right that the way we subscribe is the problem. I'm a little hesitant to type parameterize either EventBusService itself or the Initialize method inside it, as there are many more typed subscriptions being added to the MassTransit ServiceBus. Typing the service or the method specifically for one type seems awkward :) – user6473667 Jun 16 '16 at 16:03
  • You could just overload the method. – Glubus Jun 17 '16 at 07:53

1 Answers1

2

So I can tell you the short answer:

Using polymorphism in messaging contracts introduces coupling.

We believe, as MassTransit developers, that this is a bad idea. It's still possible, but not out of the box. You have to use binary serialization or a customer serializer. The default the serialization pipeline only populates a proxy of the type in the consumer.

Travis
  • 10,444
  • 2
  • 28
  • 48
  • Sounds reasonable enough. I guess neither the original developer nor I actually RTFM properly in the end. Do you have a recommendation for the current implementation (as roughly outlined above) that does *not* involve binary serialization or a customer serializer? Subscribe each type (BaseType and SubType) explicitly, and create corresponding explicit event handling methods and publishing methods? Sorry if the question is daft :) – user6473667 Jun 16 '16 at 18:21
  • There are some people who have done similar things on the MadsTransit mailing list. It depends on your end goal on how you might move forward. Just registering the consumers could be done with a container doing type scanning and a little bridge code to map them together. – Travis Jun 16 '16 at 18:24