0

Any ideas how I can fix the following error or refactor my approach to avoid it?

I have the following interfaces defined:

public interface IApiClient<TConfig, TOrder>
    where TConfig : IApiClientConfiguration
    where TOrder : IApiClientOrder
{
    TConfig Configuration { get; }

    IApiClientOrderConverter<TOrder> GetOrderConverter();

    Task<IEnumerable<TOrder>> GetNewOrdersAsync();
}

public interface IApiClientOrderConverter<TOrder>
    where TOrder : IApiClientOrder
{
    WarehouseOrder ClientOrderToWarehouseOrder(TOrder order);
}

With an implementation based on it:

public class SolidApiClient : IApiClient<SolidApiClientConfiguration, SolidOrder>
{
    public SolidApiClientConfiguration Configuration { get; }
    
    public IApiClientOrderConverter<SolidOrder> GetOrderConverter()
    {
        ...
    }
    
    public async Task<IEnumerable<SolidOrder>> GetNewOrdersAsync()
    {
        ...
    }
}

There's a function I'm trying to call with the following signature:

protected async Task ProcessClients<T>(IEnumerable<IApiClientConfiguration> clientConfigs)
    where T : IApiClient<IApiClientConfiguration, IApiClientOrder>
{
    ...
}

And I'm attempting to call it like so:

await ProcessClients<SolidApiClient>(clientsConfig);

But I'm unable to make the generic call using SolidApiClient due to the following error:

Error CS0311 The type 'SolidApiClient' cannot be used as type parameter 'T' in the generic type or method 'Function.ProcessClients(string, IEnumerable)'. There is no implicit reference conversion from 'SolidApiClient' to 'IApiClient<IApiClientConfiguration, IApiClientOrder>'.

I've seen a number of different questions posted here that are similar but I've not managed to find a resolution from the answers I've found so far so am reaching out in case someone spots something with my approach that I've missed.

I've also tried a few variations of my approach but just seem to move the issue around to different parts of the code, so believe I must be using a flawed approach with the interface classes and generics.

Gavin
  • 5,629
  • 7
  • 44
  • 86
  • [Does this answer your question?](https://stackoverflow.com/questions/2719954/understanding-covariant-and-contravariant-interfaces-in-c-sharp) – ProgrammingLlama Jul 05 '20 at 07:17
  • IApiClient at the very least can be declared as `IApiClient`. It's *possible* that it can be declared as `IApiClient` but without seeing how `IApiClientOrderConverter<>` is declared its impossible to tell (that interface may also be able to be to be declared covariant if it's not already) – pinkfloydx33 Jul 05 '20 at 10:22
  • 1
    So you can declare `IApiClientOrderConverter` as contravariant `IApiClientOrderConverter` unfortunately that means that `IApiClient` is *invariant* in `TOrder` since its both input and output. In other words neither `IApiClient` nor `IApClient` are valid. That leaves your interface as `IApiClient` which unfortunately is not going to help your problem – pinkfloydx33 Jul 05 '20 at 10:39
  • @pinkfloydx33 thank you for your insights, very much appreciated. Any rough ideas I might be able to explore to take another approach? I have some Azure Functions that do basically the same thing, importing orders from different API clients, with basically the same logic. The client's being consumed differ in implementation but the boiler plate code for the process is the same so I'm trying to figure out the best way to come up with a nice DRY solution. – Gavin Jul 05 '20 at 10:47
  • 1
    You *could* potentially define some non-generic base interfaces (ie that use `object` or `IApiClientOrder` as return/input types) and then in your classes explicitly implement them to delegate to the generic versions. You'd then work with the non-generic interfaces which can then be used without a ton of boilerplate – pinkfloydx33 Jul 05 '20 at 10:49

2 Answers2

0

Following the suggestion by @pinkfloydx33 I changed my approach to use abstract base classes instead.

As a bonus, moving away from interfaces and generics greatly simplified the solution. In my particular scenario the use of interfaces and generics was a bad choice.

Gavin
  • 5,629
  • 7
  • 44
  • 86
0

To use ProcessClients it would need to define the same generic constraints as IApiClient<...>;

ProcessClients<TClient, TConfig, TOrder>
    (IEnumerable<TConfig> clientConfigs)
    where TClient : IApiClient<TConfig, TOrder>
    where TConfig : IApiClientConfiguration
    where TOrder : IApiClientOrder

Which is feeling like a code smell. Another approach to hide this complexity is to define more interfaces, so you don't need to reference the generic interface all the time;

    interface IApiClient {}
    interface IApiClient<T> : IApiClient {}
Jeremy Lakeman
  • 9,515
  • 25
  • 29
  • Thanks for the suggestion. In my case I think the abstract base classes keep the solution much simpler and easier to maintain. They've also started to accumulate shared logic now a 2nd APiClient has been added into the mix today, so it's feeling like it's moving in a much better direction. – Gavin Jul 06 '20 at 10:11