C# foreach循环无法将类型强制转换为它实现的接口 使用完整的工作代码示例进行编辑。

C# foreach循环无法将类型强制转换为它实现的接口 使用完整的工作代码示例进行编辑。,c#,exception,covariance,C#,Exception,Covariance,在我的IRC应用程序中,应用程序从IRC服务器接收内容。内容被发送到工厂,工厂输出一个IMessage对象,应用程序的表示层可以使用该对象。IMessage接口和单个实现如下所示 public interface IMessage { object GetContent(); } public interface IMessage<out TContent> : IMessage where TContent : class { TContent Content {

在我的IRC应用程序中,应用程序从IRC服务器接收内容。内容被发送到工厂,工厂输出一个
IMessage
对象,应用程序的表示层可以使用该对象。
IMessage
接口和单个实现如下所示

public interface IMessage
{
    object GetContent();
}

public interface IMessage<out TContent> : IMessage where TContent : class
{
    TContent Content { get; }
}

public class ServerMessage : IMessage<string>
{
    public ServerMessage(string content)
    {
        this.Content = content;
    }

    public string Content { get; private set; }

    public object GetContent()
    {
        return this.Content;
    }
}
为了演示上述代码的工作原理,我有一个简单的控制台应用程序,它订阅来自上述
ServerMessage
类型的通知。控制台应用程序首先通过将
ServerMessage
对象直接传递到
Publish
方法来发布。这样做没有任何问题

第二个示例是应用程序使用工厂方法创建IMessage实例。然后,IMessage实例被传递到
Publish
方法,导致我的差异问题抛出
InvalidCastException

class Program
{
    static void Main(string[] args)
    {
        var notificationManager = new NotificationManager();
        ISubscription subscription = notificationManager.Subscribe<ServerMessage>(
            (message, sub) => Console.WriteLine(message.Content));

        notificationManager.Publish(new ServerMessage("This works"));
        IMessage newMessage = MessageFactoryMethod("This throws exception");
        notificationManager.Publish(newMessage);

        Console.ReadKey();
    }

    private static IMessage MessageFactoryMethod(string content)
    {
        return new ServerMessage(content);
    }
}
类程序
{
静态void Main(字符串[]参数)
{
var notificationManager=new notificationManager();
ISubscription subscription=notificationManager.subscription(
(message,sub)=>Console.WriteLine(message.Content));
Publish(newservermessage(“This works”);
IMessage newMessage=MessageFactoryMethod(“此抛出异常”);
notificationManager.Publish(newMessage);
Console.ReadKey();
}
私有静态IMessage MessageFactoryMethod(字符串内容)
{
返回新的ServerMessage(内容);
}
}
例外情况表明,我无法将
INotification
(发布方法所理解的是要发布的消息)强制转换为
INotification

class Program
{
    static void Main(string[] args)
    {
        var notificationManager = new NotificationManager();
        ISubscription subscription = notificationManager.Subscribe<ServerMessage>(
            (message, sub) => Console.WriteLine(message.Content));

        notificationManager.Publish(new ServerMessage("This works"));
        IMessage newMessage = MessageFactoryMethod("This throws exception");
        notificationManager.Publish(newMessage);

        Console.ReadKey();
    }

    private static IMessage MessageFactoryMethod(string content)
    {
        return new ServerMessage(content);
    }
}
我曾尝试将INotification接口标记为泛型,如
INotification
,但无法这样做,因为我正在将
TMessageType
作为
Register
方法回调的参数。我应该将接口拆分为两个单独的接口吗?一个可以注册,一个可以消费?这是最好的选择吗


任何关于这方面的额外帮助都将非常好。

这里有很长的一段路要走,因为你需要修改你提供的代码

使用断点,我可以知道该方法认为T是什么以及侦听器[messageType]的类型是什么吗

foreach (Notification<T> handler in listeners[messageType])
{
    handler.ProcessMessage(message);
}
foreach(侦听器[messageType]中的通知处理程序)
{
ProcessMessage(message);
}
因为如果一方确实是
通知
,另一方是
通知
,那么这是一个分配兼容性问题

有一个解决方案,但您还没有显示如何构建通知的代码。我将根据您当前的代码库进行推断。这应该是你所需要的

public interface INotification<in T> { /* interfacy stuff */ }
public class Notification<T>: INotification<T> { /* classy stuff */ }
public interface INotification{/*interface stuff*/}
公共类通知:INotification{/*classy stuff*/}
然后修改代码,使其基本上称为:

foreach (INotification<T> handler in listeners[messageType]) { /* loop stuff */ }
foreach(侦听器[messageType]中的INotification处理程序){/*循环内容*/}
其中监听器[messageType]必须是INotification

这应该可以避免像编译器抱怨的那样显式地将通知转换为通知

神奇之处在于INotification的接口声明中,T中的关键短语(糟糕的术语,抱歉)让编译器知道T是逆变的(默认情况下,如果省略,T是不变的,因为类型必须匹配)


编辑:根据评论,我更新了答案,以反映实际编写的代码,而不是我认为编写的代码。这主要意味着将INotification声明为逆变(in T)而不是协变(out T)。

这里的基本问题是,您试图以一种变体的方式使用您的类型,但您试图使用的语法不支持这一点。多亏了您更新的、现在已经完成的(几乎是最小的)代码示例,也很清楚您不能按照现在编写的方式来完成这项工作

所讨论的接口,特别是您想要使用的方法(即
ProcessMessage()
),实际上可以声明为协变接口(如果您将
Register()
方法拆分为单独的接口)。但这样做并不能解决您的问题

您知道,问题是您试图将
INotification
的实现分配给类型为
INotification
的变量。请注意,一旦将该实现分配给该类型的变量,调用方可以将
IMessage
的任何实例传递给该方法,即使该实例不是
S的实例ServerMessage
。但实际实现需要(不,需要!)一个
ServerMessage
的实例

public class NotificationManager
{
    private ConcurrentDictionary<Type, List<INotificationProcessor>> listeners =
        new ConcurrentDictionary<Type, List<INotificationProcessor>>();

    public ISubscription Subscribe<TMessageType>(Action<TMessageType, ISubscription> callback) where TMessageType : class, IMessage
    {
        Type messageType = typeof(TMessageType);

        // Create our key if it doesn't exist along with an empty collection as the value.
        if (!listeners.ContainsKey(messageType))
        {
            listeners.TryAdd(messageType, new List<INotificationProcessor>());
        }

        // Add our notification to our listener collection so we can publish to it later, then return it.
        var handler = new Notification<TMessageType>();
        handler.Register(callback);

        List<INotificationProcessor> subscribers = listeners[messageType];
        lock (subscribers)
        {
            subscribers.Add(handler);
        }

        return handler;
    }

    public void Publish<T>(T message) where T : class, IMessage
    {
        Type messageType = message.GetType();
        if (!listeners.ContainsKey(messageType))
        {
            return;
        }

        // Exception is thrown here due to variance issues.
        foreach (INotificationProcessor handler in listeners[messageType])
        {
            handler.ProcessMessage(message);
        }
    }
}
换句话说,您试图编写的代码不是静态安全的,在编译时无法保证类型匹配,而C#不愿意这样做


一种选择是通过使接口非泛型来削弱接口的类型安全性。即,让它始终接受
IMessage
实例。然后每个实现都必须根据其需要进行强制转换。编码错误只会在运行时被捕获,带有
InvalidCastException
,但正确的代码可以正常运行


另一个选项是设置情况,以便知道完整的类型参数。例如,也可以使用
PushMessage()
泛型方法,以便它可以使用
ServerMessage
的类型参数调用
Publish()
,而不是
IMessage

private void OnMessageProcessed(IrcMessage message, IrcCommand command, ICommandFormatter response)
{
    this.OnMessageProcessed(message);
    ServerMessage formattedMessage = (ServerMessage)response.FormatMessage(message, command);
    this.PushMessage(formattedMessage);
}

private void PushMessage<T>(T notification) where T : IMessage
{
    this.notificationManager.Publish(notification);
}
private void OnMessageProcessed(IrcMessage消息、IrcCommand命令、ICommandFormatter响应)
{
此.OnMessageProcessed(消息);
ServerMessage formattedMessage=(ServerMess
internal class Notification<TMessage> : INotificationProcessor, INotification<TMessage> where TMessage : class, IMessage
{
    private Action<TMessage, ISubscription> callback;

    public void Register(Action<TMessage, ISubscription> callbackMethod)
    {
        this.callback = callbackMethod;
    }

    public void Unsubscribe()
    {
        this.callback = null;
    }

    public void ProcessMessage(IMessage message)
    {
        // I can now cast my IMessage to T internally. This lets
        // subscribers use this and not worry about handling the cast themselves. 
        this.callback(message as TMessage, this);
    }
}
public class NotificationManager
{
    private ConcurrentDictionary<Type, List<INotificationProcessor>> listeners =
        new ConcurrentDictionary<Type, List<INotificationProcessor>>();

    public ISubscription Subscribe<TMessageType>(Action<TMessageType, ISubscription> callback) where TMessageType : class, IMessage
    {
        Type messageType = typeof(TMessageType);

        // Create our key if it doesn't exist along with an empty collection as the value.
        if (!listeners.ContainsKey(messageType))
        {
            listeners.TryAdd(messageType, new List<INotificationProcessor>());
        }

        // Add our notification to our listener collection so we can publish to it later, then return it.
        var handler = new Notification<TMessageType>();
        handler.Register(callback);

        List<INotificationProcessor> subscribers = listeners[messageType];
        lock (subscribers)
        {
            subscribers.Add(handler);
        }

        return handler;
    }

    public void Publish<T>(T message) where T : class, IMessage
    {
        Type messageType = message.GetType();
        if (!listeners.ContainsKey(messageType))
        {
            return;
        }

        // Exception is thrown here due to variance issues.
        foreach (INotificationProcessor handler in listeners[messageType])
        {
            handler.ProcessMessage(message);
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        var notificationManager = new NotificationManager();
        ISubscription subscription = notificationManager.Subscribe<ServerMessage>(
            (message, sub) => Console.WriteLine(message.Content));

        notificationManager.Publish(new ServerMessage("This works"));
        IMessage newMessage = MessageFactoryMethod("This works without issue.");
        notificationManager.Publish(newMessage);

        Console.ReadKey();
    }

    private static IMessage MessageFactoryMethod(string content)
    {
        return new ServerMessage(content);
    }
}