Why does implementing an interface on a subclass of EventSource throw an exception at runtime?
Asked Answered
L

5

8

I'm trying to use Event Tracing for Windows (ETW) in my .NET application via the EventSource class that was included in .NET 4.5. I'm subclassing EventSource as MyEventSource and trying to implement an interface IMyEventSource (for mocking purposes) as follows:

public interface IMyEventSource
{
  void Test();
}

public class MyEventSource : EventSource, IMyEventSource
{
  public static MyEventSource Log = new MyEventSource();

  [Event(1)]
  public void Test()
  {
    this.WriteEvent(1);
  }
}

When I run PerfView and execute this code, I get an IndexOutOfRangeException on the call to WriteEvent. If I remove the interface by modifying the code...

public class MyEventSource : EventSource
{
  public static MyEventSource Log = new MyEventSource();

  [Event(1)]
  public void Test()
  {
    this.WriteEvent(1);
  }
}

...then everything works just fine.

Here is the code I used for testing in both cases:

static void Main(string[] args)
{
  MyEventSource.Log.Test();
}

Why does my subclass of EventSource break if it simply implements an interface?

Here is a related post.

Liriodendron answered 30/4, 2013 at 13:48 Comment(0)
B
12

At time of asking the question, the answer from @LarsSkovslund was correct. However with the stable version of Microsoft.Diagnostics.Tracing.EventSource, Microsoft changed this according to their blog post:

With the RTM release we’ve relaxed some of the event source validation rules in order to enable certain advanced usage scenarios.

The two changes in this area:

  • EventSource types may now implement interfaces to enable the use of event source types in advanced logging systems that use interfaces to define a common logging target.

  • The concept of a utility event source type (defined as an abstract class deriving from EventSource) is introduced to support sharing code across multiple event source types in a project (e.g. for optimized WriteEvent() overloads).

System.Diagnostics.Tracing.EventSource, provided with the .NET Framework, supports these scenarios as of .NET 4.6

Boatwright answered 19/4, 2014 at 9:3 Comment(5)
It's important to note that this is only true for the NuGet version of EventsSource (i.e. Microsoft.Diagnostics.Tracing.EventSource). The built-in version that comes with .NET 4.5 (i.e. System.Diagnostics.Tracing.EventSource) still has this limitation.A1
I talk about the NuGet Version (Microsoft), not the System version.Boatwright
Yes, I know, but that may not be apparent to anyone reading this answer.A1
read the answer again. It clearly states "Microsoft.Diagnostics.Tracing.EventSource"Boatwright
Yes, only it's not in bold and easy to miss. And even if it were emphasized, anyone who isn't familiar with the NuGet variety of EventSource would probably miss it anyway. The wording of your answer is a bit misleading as well, suggesting that it used to be X and now it is Y. In reality, the officially recommended release is still X, but there is a different, out-of-band release, that enables Y. Don't get me wrong - it's great that you pointed it out, I just wanted to make this distinction clear.A1
Q
5

While you can't have the event source implement an interface, it is possible to wrap it with another class that does. (This is an instance of the Adapter design pattern)

public class EventSourceAdapter : IEventSource
{
    private MyEventSource log;

    public EventSourceAdapter(MyEventSource log)
    {
        this.log = log;
    }

    public void Test()
    { 
        log.Test()
    }
}
} 
Qadi answered 16/5, 2013 at 7:18 Comment(0)
M
4

When the EventSource class is building up its event structure base on reflection it will consider direct methods only e.g. inherited members are not considered as in your case with the use of IMyEventSource.

You are getting the IndexOutOfRangeException because WriteEvent will use the event id parameter to lookup a descriptor block with an index matching the event id thus throwing the exception when index does not exist.

So in short DONT used interfaces to define your ETW events using EventSource.

Cheers Lars

Midlands answered 1/5, 2013 at 7:15 Comment(4)
Thank you for your response Lars. It brings up a couple questions ... 1. Why does this continue to be broken if I specify event ids explicitly (added to the code sample above)? 2. Any recommendations for how to mock an EventSource? I want my unit tests to verify that a particular event is being triggered upon certain conditions.Liriodendron
Hi Mike, 1) Explicitly declaring the event id is not going to help as the underlying implementation uses reflection to get methods using the BindingFlags.DeclaredOnly (MSDN: "Specifies that only members declared at the level of the supplied type's hierarchy should be considered. Inherited members are not considered.") 2) The only way as I see it is through delegation e.g. implement the interface explicitly IMyEventSource.Test() and then call Test() from there.Midlands
@Liriodendron the stable version of EventSource now allows Interface: blogs.msdn.com/b/dotnet/archive/2014/01/30/… "EventSource types may now implement interfaces to enable the use of event source types in advanced logging systems that use interfaces to define a common logging target."Boatwright
@Boatwright Great find, if you post that as an answer I'll accept it.Liriodendron
G
1

As of today (Sept 29, 2014), the code the original poster provided does not work with the native code that ships with .NET 4.5. It still generates an "IndexOutOfRange" exception, and as he says, it does this only if the ETW events are being monitored (I'm using PerfView).

That said, I checked with .NET version 4.0 using the Microsoft EventSource Library from nuget.org, and his code does work with that.

I next installed Microsoft EventSource Library from nuget in a .NET version 4.5 project. I made sure to inherit from Microsoft.Diagnostics.Tracing.EventSource and not from System.Diagnostics.Tracing.EventSource from the native .NET 4.5 library. This worked, but I also found that I had to mark those methods that inherited from the interface with the [Microsoft.Diagnostics.Tracing.Event(int)] attribute.

I have also observed some strange behaviors I couldn't explain. Sometimes some of my events show up in PerfView as being named "EventID(0)" instead of the method name. Sometimes I got unexpected IndexOutOfRange exceptions. As best I can guess the registration from a previous trial remained in memory. I started renaming my EventSource class between trials, and I wasn't getting these issues anymore.

JR

Geesey answered 29/9, 2014 at 19:25 Comment(2)
You'll get more attention if you ask your question in a new question instead of putting it in as an answer.Carse
Thanks, but I'm not really interested in the strange behaviors I found. I wanted to add my answer to this question because I thought the answers provided did not fully cover how to use interfaces with EventSource classes, and I wanted to make sure that someone else with a similar problem could benefit from my experience. If I put it in a new question, it would be the same question and so redundant.Geesey
M
0

There is a workaround for this issue (sorry I don't know how to explain the problem). If you're method is decorated with the NonEventAttribute you will be able to use your interface.

Your interface :

public interface IMyEventSource
{
  void Test();
}

And the implementation :

public class MyEventSource : EventSource, IMyEventSource
{
    public static MyEventSource Log = new MyEventSource();

    [NonEvent]
    public void Test()
    {
        this.InternalTest();
    }

    [Event(1)]
    private void InternalTest()
    {
        this.WriteEvent(1);
    }
}
Monjo answered 15/12, 2015 at 9:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.