C
C#7mo ago
Warcaith

Issue with generic interfaces (covariance/contravariance)

Hello! Currently, I have these interfaces within my project:
c#
public interface IResult<out TResponse>
{
public bool Success { get; }
public TResponse? Response { get; }
}

public interface ICommand<in TOptions, out TResponse>
{
IResult<TResponse?> Execute(TOptions options);
}

public interface ICommand<in TOptions>
{
IResult<object?> Execute(TOptions options);
}

public interface ICommand
{
IResult<object?> Execute(object options);
}

public class CommandOptions
{

}

public class Command : ICommand<CommandOptions>
{
public IResult<object?> Execute(CommandOptions options)
{
}
}
c#
public interface IResult<out TResponse>
{
public bool Success { get; }
public TResponse? Response { get; }
}

public interface ICommand<in TOptions, out TResponse>
{
IResult<TResponse?> Execute(TOptions options);
}

public interface ICommand<in TOptions>
{
IResult<object?> Execute(TOptions options);
}

public interface ICommand
{
IResult<object?> Execute(object options);
}

public class CommandOptions
{

}

public class Command : ICommand<CommandOptions>
{
public IResult<object?> Execute(CommandOptions options)
{
}
}
However, I'm unable to create a command like this:
c#
object options = new CommandOptions();
ICommand<object> command = new Command();
command.Execute(options);
c#
object options = new CommandOptions();
ICommand<object> command = new Command();
command.Execute(options);
Cannot implicitly convert type 'Command' to 'ICommand<object>'. An explicit conversion exists (are you missing a cast?)
Cannot implicitly convert type 'Command' to 'ICommand<object>'. An explicit conversion exists (are you missing a cast?)
... which makes it impossible for me to create a command and options dynamically. I have probably a design issue within my interfaces, so please, if there's anything that should be changed, don't hesitate to make these changes. Thanks! 😁 (Downcasting to ICommand<object> gives me an System.InvalidCastException)
1 Reply
Warcaith
Warcaith7mo ago
Yeah, it does makes sense... I'll probably just need to rethink how these commands are created. Currently, I was looking to create a factory method that could create the "Command" and corresponding "Options" by a specific unique (string) name, but since I can't return something generic and always have to specify which sort of "TOptions" the command will use, the factory won't work. Yeah, I actually did exactly that after my post, however, when I tried to cast it, it still gave me the exception. I'll see if there's something weird with my implementation Here's basically what I was trying to do:
1. Program.exe MyCommandName
2. MyCommandName - Goes into -> FactoryMethod(MyCommandName)
3. FactoryMethod(MyCommandName) - Returns -> Command, Options
1. Program.exe MyCommandName
2. MyCommandName - Goes into -> FactoryMethod(MyCommandName)
3. FactoryMethod(MyCommandName) - Returns -> Command, Options
But as you said, it won't work, since I can't return anything generic enough. Do you happen to know why it doesn't work to cast to the interface? Yeah, right, seems a bit unsafe.. 😄 However, I do have this abstract class:
public abstract class Command<TOptions, TResponse> : ICommand<TOptions, TResponse>
{
public abstract IResult<TResponse?> Execute(TOptions options);

public IResult<TResponse?> Execute(object options)
{
return Execute((TOptions)options);
}
}

public abstract class Command<TOptions> : ICommand<TOptions>
{
public abstract IResult<object?> Execute(TOptions options);

public IResult<object?> Execute(object options)
{
return Execute((TOptions)options);
}
}

public abstract class Command : ICommand
{
public abstract IResult<object?> Execute(object options);
}
public abstract class Command<TOptions, TResponse> : ICommand<TOptions, TResponse>
{
public abstract IResult<TResponse?> Execute(TOptions options);

public IResult<TResponse?> Execute(object options)
{
return Execute((TOptions)options);
}
}

public abstract class Command<TOptions> : ICommand<TOptions>
{
public abstract IResult<object?> Execute(TOptions options);

public IResult<object?> Execute(object options)
{
return Execute((TOptions)options);
}
}

public abstract class Command : ICommand
{
public abstract IResult<object?> Execute(object options);
}
I mean, I can't cast to that class either.. is it because it's an abstract class? They do have relation For example, this doesn't work:
c#
class MyCommand : Command<CommandOptions>
{
...
}

ICommand<object> command = (Command<object>)new MyCommand();
c#
class MyCommand : Command<CommandOptions>
{
...
}

ICommand<object> command = (Command<object>)new MyCommand();
Hmm, I see... Yeah, either way, there's something missing from my design that I need to figure out 😄 Would this make sense @eXtensible Style Sheets?
c#
private static IResult<object?> ExecuteCommand(string name, string[] args)
{
switch(name)
{
case "Deploy":
return (new DeployCommand())
.Execute(CreateOptions<DeployOptions>(args));
case "Launch":
return (new LaunchCommand())
.Execute(CreateOptions<LaunchOptions>(args));
default:
throw new ArgumentException();
}
}
}
c#
private static IResult<object?> ExecuteCommand(string name, string[] args)
{
switch(name)
{
case "Deploy":
return (new DeployCommand())
.Execute(CreateOptions<DeployOptions>(args));
case "Launch":
return (new LaunchCommand())
.Execute(CreateOptions<LaunchOptions>(args));
default:
throw new ArgumentException();
}
}
}
Yeah, but I do need to register them somewhere. Any suggestion for making it better? I'm eager to learn 🙂 I mean, I need a registry somewhere, of some sort. All commands are of type "Command", this one:
C#
public abstract class Command<TOptions, TResponse> : ICommand<TOptions, TResponse>
{
public abstract IResult<TResponse?> Execute(TOptions options);

public IResult<TResponse?> Execute(object options)
{
return Execute((TOptions)options);
}
}

public abstract class Command<TOptions> : ICommand<TOptions>
{
public abstract IResult<object?> Execute(TOptions options);

public IResult<object?> Execute(object options)
{
return Execute((TOptions)options);
}
}

public abstract class Command : ICommand
{
public abstract IResult<object?> Execute(object options);
}
C#
public abstract class Command<TOptions, TResponse> : ICommand<TOptions, TResponse>
{
public abstract IResult<TResponse?> Execute(TOptions options);

public IResult<TResponse?> Execute(object options)
{
return Execute((TOptions)options);
}
}

public abstract class Command<TOptions> : ICommand<TOptions>
{
public abstract IResult<object?> Execute(TOptions options);

public IResult<object?> Execute(object options)
{
return Execute((TOptions)options);
}
}

public abstract class Command : ICommand
{
public abstract IResult<object?> Execute(object options);
}
However... now we are back at the casting problems:
c#

public LaunchOptions {}

public LaunchCommand : Command<LaunchOptions> {}

List<Command<object>> commands = new List<Command<object>>();
commands.Add(new LaunchCommand());
c#

public LaunchOptions {}

public LaunchCommand : Command<LaunchOptions> {}

List<Command<object>> commands = new List<Command<object>>();
commands.Add(new LaunchCommand());
How would I then specify which sort of options the command will use? So basically, the "Execute" method will have no options parameter, but the class itself will have the options in a property? Oh, I see!
Want results from more Discord servers?
Add your server