为什么在EventSource子类上实现接口会在运行时抛出异常?

8
我正在尝试通过.NET 4.5中包含的EventSource类,在我的.NET应用程序中使用Windows事件跟踪(ETW)。我将EventSource作为MyEventSource进行子类化,并尝试实现一个名为IMyEventSource的接口(用于模拟目的)。
public interface IMyEventSource
{
  void Test();
}

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

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

当我运行PerfView并执行以下代码时,会在调用WriteEvent时出现IndexOutOfRangeException。如果我通过修改代码来删除接口...
public class MyEventSource : EventSource
{
  public static MyEventSource Log = new MyEventSource();

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

如果一切正常,那么一切都会顺利运行。

以下是我用于测试两种情况的代码:

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

如果我的EventSource子类仅实现一个接口,为什么会出错?

这里有一个相关帖子

5个回答

12

在提问时,@LarsSkovslund的回答是正确的。然而,随着Microsoft.Diagnostics.Tracing.EventSource的稳定版本发布,微软 根据他们的博客文章进行了更改:

我们在RTM版本中 放宽了某些事件源验证规则,以便启用某些高级使用场景。

在这个领域有两个变化:

  • 现在可以使用事件源类型实现接口,以便在使用接口定义公共日志目标的高级日志系统中使用事件源类型。

  • 引入了实用事件源类型的概念(定义为从EventSource派生的抽象类),以支持在项目中跨多个事件源类型共享代码(例如,用于优化WriteEvent()重载)。

System.Diagnostics.Tracing.EventSource,是.NET Framework提供的,截至.NET 4.6,支持以下方案。


3
需要注意的是,这仅适用于 NuGet 版本的 EventsSource(即 Microsoft.Diagnostics.Tracing.EventSource)。随 .NET 4.5 一起提供的内置版本(即 System.Diagnostics.Tracing.EventSource)仍然存在此限制。 - Ohad Schneider
我所说的是NuGet版本(Microsoft),而不是系统版本。 - magicandre1981
是的,我知道,但对于阅读此答案的任何人来说可能并不明显。 - Ohad Schneider
请再读一遍答案。它清楚地指出了 "Microsoft.Diagnostics.Tracing.EventSource"。 - magicandre1981
是的,只是它没有加粗显示,很容易被忽视。即使强调了,任何不熟悉NuGet事件源的人可能也会忽略它。你回答的措辞有点误导,表明它曾经是X,现在变成了Y。实际上,官方推荐的版本仍然是X,但是有一个不同的、非正式的版本发布,可以启用Y。别误解我的意思——你指出这一点很好,我只是想澄清这个区别。 - Ohad Schneider

5

虽然你不能让事件源实现一个接口,但是可以使用另一个类来包装它并实现接口。(这是适配器设计模式的一个实例)

public class EventSourceAdapter : IEventSource
{
    private MyEventSource log;

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

    public void Test()
    { 
        log.Test()
    }
}
} 

4
当 EventSource 类根据反射构建其事件结构时,仅考虑直接方法,例如在使用 IMyEventSource 时不考虑继承成员。
由于 WriteEvent 将使用事件 ID 参数来查找具有与事件 ID 匹配的索引的描述符块,因此当索引不存在时会抛出 IndexOutOfRangeException 异常。
简而言之,不要使用接口来定义使用 EventSource 的 ETW 事件。
干杯 Lars

谢谢您的回复,Lars。这引出了一些问题... 1.如果我明确指定事件ID(添加到上面的代码示例中),为什么它仍然无法正常工作?2.有没有关于如何模拟EventSource的建议?我希望我的单元测试能够验证在特定条件下触发了特定事件。 - Mike
嗨,Mike,1)明确声明事件ID是没有帮助的,因为底层实现使用反射来获取方法,使用BindingFlags.DeclaredOnly(MSDN:“指定只应考虑在提供的类型层次结构级别上声明的成员。不考虑继承的成员。”)2)我所看到的唯一方法是通过委托,例如显式实现“IMyEventSource.Test()”,然后从那里调用“Test()”。 - Lars Skovslund
1
@Mike 稳定版本的EventSource现在允许接口:http://blogs.msdn.com/b/dotnet/archive/2014/01/30/microsoft-diagnostics-tracing-eventsource-rtms.aspx?Redirected=true "EventSource类型现在可以实现接口,以启用在使用接口定义公共日志目标的高级日志系统中使用事件源类型。" - magicandre1981
@magicandre1981 很好的发现,如果你把它作为答案发布,我会接受它。 - Mike

1
截至今天(2014年9月29日),原帖提供的代码与.NET 4.5自带的本机代码不兼容。它仍会生成“IndexOutOfRange”异常,并且正如他所说,只有在监视ETW事件时才会发生这种情况(我正在使用PerfView)。
话虽如此,我使用nuget.org中的Microsoft EventSource Library检查了.NET版本4.0,并且他的代码确实可以使用。
接下来,我在.NET版本4.5项目中安装了Microsoft EventSource Library from nuget。我确保从Microsoft.Diagnostics.Tracing.EventSource继承而不是从本机.NET 4.5库的System.Diagnostics.Tracing.EventSource继承。这样做是有效的,但我还发现必须使用[Microsoft.Diagnostics.Tracing.Event(int)]属性标记那些从接口继承的方法。
我还观察到了一些我无法解释的奇怪行为。有时,我的某些事件在PerfView中显示为“EventID(0)”而不是方法名。有时我会得到意外的IndexOutOfRange异常。据我所知,先前试验的注册仍然留存在内存中。我开始在试验之间重命名我的EventSource类,然后我再也没有遇到这些问题了。
JR

如果您将问题作为答案提交,而不是新问题进行提问,那么您将得到更多的关注。 - Lars Truijens
1
谢谢,但我对我发现的奇怪行为并不真正感兴趣。我想回答这个问题是因为我认为提供的答案没有完全涵盖如何使用接口与EventSource类,并且我希望确保其他遇到类似问题的人可以从我的经验中受益。如果我把它放在一个新问题中,那么它将是相同的问题,因此是多余的。 - jrv

0

针对这个问题有一个解决方法(抱歉我不知道如何解释这个问题)。 如果您的方法被装饰了NonEventAttribute,那么您将能够使用您的接口。

您的接口:

public interface IMyEventSource
{
  void Test();
}

实现方式如下:

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);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接