I'm trying to achieve something that I'm not sure is possible and I'm a bit stuck.
I have some base types called Client and Server defined like this:
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){}
}
These classes are later expanded in a different assembly like this:
public class LobbyClient : Client
{
string username;
string passwordHash;
}
public class LobbyServer : Server<LobbyClient>
{
public override void ClientConnected(LobbyClient client)
{
Console.WriteLine("Client connected");
}
}
From the first assembly I dynamically load the second one where my base classes are derived.
I'm then trying to do this:
Server<Client> server = Activator.CreateInstance(serverTypeInfo);
but without any luck as the conversion is invalid.
I also want to do something like this later in the code:
Client client = Activator.CreateInstance(clientType) as Client;
server.ClientConnected(client);
I tried making further base interfaces IClient and IServer and derive Client and Server from those and setting the template argument as in but that did not work.
Is there any way in which I could achieve my goal here?
I see Player.IO (now Yahoo Games network) managed to do this but I can't figure how the code works looking just at the compiled assembly.
https://gamesnet.yahoo.net/documentation/services/multiplayer/serverside
EDIT:
Here is the version with the interfaces that I tried:
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){}
}
And later in the code:
IServer<IClient> server = Activator.CreateInstance(serverTypeInfo);
Thank you
Co- and contra-variance will not work in this case, as you are attempting to supply a less derived parameter as input into the implementation. Also, co- an contra-variance only work via interfaces, which have had their type parameters declared with the
inoroutkeywords.For example (pseudo types here for illustration):
The
Server<LobbyClient>cannot be cast into aIServer<Client>, because it expects as input LobbyClient instances, but the interface could potentially pass in anyClienttype.There are two ways that I can think of to work around this, but both involve subverting the type system; so you need to make sure yourself that the types being used are correct, else you will get runtime exceptions.
The first method is to call your server methods via reflection. This can be both quite slow and verbose, however.
The second method would be to write a non-generic interface for your Server class, and have your Sever class explicitly implement it, while delegating each method to the corresponding generic implementation.
Now, the non-generic methods will not normally show up on your server implementations when accessed directly, but you will be able to assign instances to the
IServerinterface and call those non-generic methods. You will get runtime exceptions if the types do not match up correctly.