【问题标题】:Compile-time information lost with conversion to object, cannot cast to generic interface<T>编译时信息因转换为对象而丢失,无法转换为通用接口<T>
【发布时间】:2013-05-16 06:43:40
【问题描述】:

我有一个实现泛型接口的具体类型化版本的类。我发现如果我在编译时将一个对象传递给我的函数(即使它可能是正确的对象),它仍然被认为是一个对象,因此在运行时失败并出现错误:

Unable to cast object of type 'TestEventHandler' to type 'IDomainEventHandler '1[System.Object]'.

我正在反序列化来自总线的消息(哪些产品对象,应​​该有一个关联的 DomainEventHandler&lt;of_deserialized_type&gt; 与消息关联。

总之,我认为问题是IDomainEventHandler&lt;T&gt; 不是从`IDomainEventHandler&lt;object&gt; 转换的,我希望得到有关如何最好地解决此问题并仍然保持通用IDomainEventHandler&lt;T&gt; 接口的指导,即使将对象传递给@987654327 @。

[TestClass]
public class InternalMessageHandlerTests
{
    class TestEvent
    {
    }

    class TestEventHandler : IDomainEventHandler<TestEvent>
    {
        public void HandleEvent(TestEvent domainEvent)
        {
        }
    }

    [TestMethod]
    public void Test()
    {
        TestEvent testEvent = new TestEvent();
        object testEventAsObject = testEvent; // compile time type information lost 
        Publish(testEvent); // this is OK :)
        Publish(testEventAsObject); // this fails :(

    }

    public void Publish<T>(T eventToPublish) where T : class
    {
        var handlerInstance = new TestEventHandler();
        IDomainEventHandler<T> converted = (IDomainEventHandler<T>)handlerInstance;
        converted.HandleEvent(eventToPublish);
    }
}

【问题讨论】:

标签: c#


【解决方案1】:

您不能将TestEventHandler 转换为IDomainEventHandler&lt;object&gt;,因为这不安全。如果允许,您可以这样做:

IDomainEventHandler<object> converted = (IDomainEventHandler<object>)new TestEventHandler();
converted.HandleEvent("dsfs");

这是无效的,因为 TestEventHandler 要求 HandleEvent 的参数为 TestEvent

您可以使用反射调用Publish

TestEvent testEvent = new TestEvent();
object testEventAsObject = testEvent;
var publishMethod = this.GetType().GetMethod("Publish").MakeGenericMethod(testEventAsObject.GetType());
publishMethod.Invoke(this, new object[] { testEventAsObject });

【讨论】:

  • 谢谢 Lee 反射比本地调用慢很多?我不想过早地进行优化,但由于这是总线消息传递,每秒会有相当多的消息通过
  • @g18c - 是的,反射比直接调用要慢很多,所以如果你有很多消息,这可能是个问题 - 我从你的例子中认为这只是测试代码。如果这种方法太慢,您可以定义一个非通用的IDomainEventHandler 来处理object 消息并在处理程序中转换为预期的消息类型。然后你只需要测试处理程序映射。
  • 只是为了更新,我继续使用已知编译时类型 T 的通用定义来进行内部消息传递和事件,因为类型已知正确,这些将很快发光。对于从总线传入的对象类型,我根据您的代码通过反射调用,虽然起初我在考虑现实中的速度,因为消息来自网络总线(即慢速)反射仍然比总线快很多数量级。因此,最终我在速度(用于处理中的消息传递)和调度时的灵活性(使用较慢的总线)方面得到了两全其美的结果! :)
【解决方案2】:

在这种情况下,不可能支持没有反射的完全通用接口。对于这样一个简单的案例,反射是一个非常糟糕的主意。

对于这些情况,我通常的做法是IDomainEventHandler,然后是DomainEventHandlerBase&lt;T&gt; : IDomainEventHandler,这样继承类就可以充分利用泛型,但外部接口可以接受对象。

显然它不是静态安全的,但正如我所说,这里不可能是静态安全的。将实例分配给 object testEventAsObject 后,您就可以让此变量包含从编译器角度来看的任何内容(stringint 等任何内容)。

对于必须选择正确处理程序的服务总线之类的事情,它看起来像这样:

public interface IDomainEventHandler {
    void HandleEvent(object domainEvent);
    bool CanHandleEvent(object domainEvent);
}

public abstract class DomainEventHandlerBase<T> : IDomainEventHandler {
    public abstract void HandleEvent(T domainEvent);
    public abstract bool CanHandleEvent(T domainEvent);

    void IDomainEventHandler.HandleEvent(object domainEvent) {
        return HandleEvent((T)domainEvent);
    }

    bool IDomainEventHandler.CanHandleEvent(object domainEvent) {            
        return (domainEvent is T) && CanHandleEvent((T)domainEvent);
    }
}

我是直接写的(没有在 VS 中检查)所以可能会出错。

之后,当您收到消息时,只需选择

handlers.First(h => h.CanHandleEvent(domainEvent))

【讨论】:

  • 嗨 Andrey,所以基类会将类型转换为正确的事件类型?如果你有时间可以放弃一个例子,将不胜感激
  • @g18c 当然,添加了一个示例(对于任何可能的错误,我们深表歉意)。
猜你喜欢
  • 2016-08-02
  • 2018-11-15
  • 1970-01-01
  • 1970-01-01
  • 2019-05-08
  • 2012-03-16
  • 2012-11-07
  • 1970-01-01
相关资源
最近更新 更多