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
If you want a specialised implementation of
Server
orIServer
, likeLobbyServer
to handle connections to anyClient
, then you must specify it appropriately. If you do, then you will be able to treat that instance as a genericServer<Client>
orIServer<Client>
.e.g.
if
LobbyServer
were declared thus,then
will execute without an
InvalidCastException
.If
LobbyServer
is defined with a more specialised type thenClient
, likeLobbyClient
, then it is not aServer<Client>
and cannot be cast to that type.The answer is to make
TClient
covariant but, as you've discovered, with your interface declared the way it is,TClient
needs to be contravariant to maintain input safety. You can overcome that like this.here, the
Action
delagate reverses the direction of variance maintaining input safety.Now
LobbyServer
can be declared like this,and
is perfectly valid.
However, the implementation of the actions is delegated till when the types are known.