我正在努力实现一些我不确定是可行的事情而且我有点陷入困境。
我有一些名为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);
谢谢
答案 0 :(得分:1)
在这种情况下,Co-和contra-variance将不起作用,因为您试图提供较少派生的参数作为实现的输入。此外,共同的逆变量只能通过接口工作,接口的类型参数已使用in
或out
关键字声明。
例如(伪类型在这里用于说明):
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)
如果您想要Server
或IServer
的专门实施,例如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));
完全有效。
但是,行动的实施将被委派,直到知道类型为止。