C#中的协方差和逆变使用

时间:2014-11-19 09:38:12

标签: c# generics interface covariance contravariance

我正在努力实现一些我不确定是可行的事情而且我有点陷入困境。

我有一些名为Client和Server的基类型定义如下:

public class Client 
{

}
public class Server<ClientTemplate>  
    where ClientTemplate : Client
{
    public virtual void ClientConnected(ClientTemplate client){}
    public virtual void ClientDisconnected(ClientTemplate client){}
    public virtual void MessageReceived(ClientTemplate client, Message message){}
    public virtual void SendMessage(ClientTemplate client, Message message){}
}

这些类后来在不同的程序集中扩展,如下所示:

public class LobbyClient : Client
{
    string username;
    string passwordHash;
}
public class LobbyServer : Server<LobbyClient>
{
    public override void ClientConnected(LobbyClient client)
    {
        Console.WriteLine("Client connected");
    }
}

从第一个程序集中,我动态加载导出基类的第二个程序集。

我正在尝试这样做:

Server<Client> server = Activator.CreateInstance(serverTypeInfo);

但没有任何运气,因为转换无效。

我还希望稍后在代码中执行类似的操作:

Client client = Activator.CreateInstance(clientType) as Client;
server.ClientConnected(client);

我尝试进一步创建基本接口IClient和IServer,并从中导出客户端和服务器,并将模板参数设置为但不起作用。

我有什么方法可以实现我的目标吗?

我看到Player.IO(现在雅虎游戏网络)设法做到这一点,但我无法弄清楚代码是如何看待编译程序集的。

https://gamesnet.yahoo.net/documentation/services/multiplayer/serverside

编辑:

以下是我尝试过的接口版本:

public interface IClient
{

}

public class Client : IClient
{

}

interface IServer<in ClientTemplate> where ClientTemplate : IClient
{
    void ClientConnected(ClientTemplate client);
    void ClientDisconnected(ClientTemplate client);
    void MessageReceived(ClientTemplate client, Message message);
    void SendMessage(ClientTemplate client, Message message);
}

public class Server<ClientTemplate> : IServer<ClientTemplate>
    where ClientTemplate : IClient
{
    public virtual void ClientConnected(ClientTemplate client){}
    public virtual void ClientDisconnected(ClientTemplate client){}
    public virtual void MessageReceived(ClientTemplate client, Message message){}
    public virtual void SendMessage(ClientTemplate client, Message message){}
}

后来在代码中:

IServer<IClient> server = Activator.CreateInstance(serverTypeInfo);

谢谢

3 个答案:

答案 0 :(得分:1)

在这种情况下,Co-和contra-variance将不起作用,因为您试图提供较少派生的参数作为实现的输入。此外,共同的逆变量只能通过接口工作,接口的类型参数已使用inout关键字声明。

例如(伪类型在这里用于说明):

IServer<Client> server = new Server<LobbyClient>();
Client client = new GameClient();
server.ClientConnected(client);

Server<LobbyClient>无法转换为IServer<Client>,因为它需要输入LobbyClient实例,但接口可能会传入任何 Client类型。

我可以通过两种方式来解决这个问题,但两者都涉及颠覆类型系统;所以你需要确保自己使用的类型是正确的,否则你将获得运行时异常。

第一种方法是通过反射调用服务器方法。然而,这可能既缓慢又冗长。

第二种方法是为您的Server类编写一个非泛型接口,让Sever类显式实现它,同时将每个方法委托给相应的泛型实现。

public interface IServer
{
    void ClientConnected(Client client){}
    void ClientDisconnected(Client client){}
    void MessageReceived(Client client, Message message){}
    void SendMessage(Client client, Message message){}
}

public class Server<ClientTemplate> : IServer
    where ClientTemplate : Client
{
    void IServer.ClientConnected(Client client)
    {
        ClientConnected((ClientTemplate)client);
    }

    void IServer.ClientDisconnected(Client client)
    {
        ClientDisconnected((ClientTemplate)client);
    }

    void IServer.MessageReceived(Client client, Message message)
    {
        MessageReceived((ClientTemplate)client, message);
    }

    void IServer.SendMessage(Client client, Message message)
    {
        SendMessage((ClientTemplate)client, message);
    }

    public virtual void ClientConnected(ClientTemplate client){}
    public virtual void ClientDisconnected(ClientTemplate client){}
    public virtual void MessageReceived(ClientTemplate client, Message message){}
    public virtual void SendMessage(ClientTemplate client, Message message){}
}

现在,直接访问时,非泛型方法通常不会显示在您的服务器实现上,但您可以将实例分配给IServer接口并调用这些非泛型方法。如果类型不正确匹配,您将获得运行时异常。

IServer server = Activator.CreateInstance(serverTypeInfo) as IServer;    
Client client = Activator.CreateInstance(clientType) as Client;
server.ClientConnected(client); // works

答案 1 :(得分:1)

我认为不可能,
好像您将LobbyServer转换为Server [Client],可能会出现运行时错误。

e.g。

Server<Client> server = new LobbyServer();
server.ClientConnected(new Client()); // how can a LobbyServer handle this?

抱歉,没有给你答案。

答案 2 :(得分:0)

如果您想要ServerIServer的专门实施,例如LobbyServer来处理与任何Client的连接,那么您必须适当地指定它。如果您这样做,那么您将能够将该实例视为通用Server<Client>IServer<Client>

<强> e.g。

如果声明LobbyServer

public class LobbyServer : Server<Client>
{
    public override void ClientConnected(Client client)
    {
        // ...
    }
}

然后

var instance = (Server<Client>)Activator.CreateInstance(typeof(LobbyServer));

将在没有InvalidCastException的情况下执行。

如果使用更专业的类型定义LobbyServer,那么Client就像LobbyClient一样,那么不是一个Server<Client>而不能转为那种类型。


答案是使TClient协变,但正如您所发现的那样,您的界面被声明的方式,TClient需要逆变以保持输入安全。你可以这样克服它。

interface IServer<out TClient> where TClient : IClient
{
    void ClientConnected(
            Action<TClient, IServer<TClient>> connectionAction);
    void ClientDisconnected(
            Action<TClient, IServer<TClient>> disconnectionAction);
    void MessageReceived(
           Action<TClient, IServer<TClient>, Message> recievedAction);
    void SendMessage(
           Action<TClient, IServer<TClient>, Message> sendAction);
}

这里,Action delagate会改变维持输入安全的方差方向。

现在LobbyServer可以像这样声明,

public class LobbyServer : Server<LobbyClient>
{
}

var instance (IServer<IClient>)Activator.CreateInstance(typeof(LobbyServer));

完全有效。

但是,行动的实施将被委派,直到知道类型为止。