ganchito55
ganchito55

Reputation: 3607

Creating a generic service interface that uses generic params

I'm trying to use the c# generics to create a generic interface that works as a base to build all my listing services. However I have a problem with the definition.

The base classes

abstract class ListingParams { }

abstract class ListingDTO<T> where T : ListingItemDTO
{
    public List<T> Items{ get; set; }
}

abstract class ListingItemDTO { }

A concrete example

class HListingParams : ListingParams { }
class HListing : ListingDTO<HItem> { }
class HItem : ListingItemDTO { }

The interface

interface IListingToolsService<in TFilter, out U, V>
    where TFilter : ListingParams
    where U : ListingDTO<V> where V : ListingItemDTO
{
    int Count(TFilter parameters);

    U Get(TFilter parameters);
}

Here starts the problem, due to the Get method returns a generic type, I have to add a third generic parameter to the interface.

If I want to create a concrete implementation of that interface I have to create something like this:

class HListingToolsService : IListingToolsService<ListingParams, HListing, HItem>
{
    public int Count(ListingParams parameters) => throw new NotImplementedException();
    public HListing Get(ListingParams parameters) => throw new NotImplementedException();
}

However by definition HListing is not generic, due to it was defined using HItem.

Is there any way to create that interface with only two parameters in order to don't repeat a type that is already defined?

Upvotes: 2

Views: 608

Answers (2)

Pavel Anikhouski
Pavel Anikhouski

Reputation: 23228

You can change your code a little bit, convert ListingDTO<T> to interface and make generic type parameter T covariant. In this case you also should change Items type to IEnumerable<T>, since List<T> is invariant.

interface IListingDTO<out T> where T : ListingItemDTO
{
    public IEnumerable<T> Items { get; }
}

Then implement it in HListing class

class HListing : IListingDTO<HItem>
{
    public IEnumerable<HItem> Items { get; }
}

After that you can update IListingToolsService interface and get rid of V generic type parameter and its constraint

interface IListingToolsService<in TFilter, out U>
    where TFilter : ListingParams
    where U : IListingDTO<ListingItemDTO>
{
    int Count(TFilter parameters);

    U Get(TFilter parameters);
}

Finally implement HListingToolsService class

class HListingToolsService : IListingToolsService<ListingParams, HListing>
{
    public int Count(ListingParams parameters) => throw new NotImplementedException();
    public HListing Get(ListingParams parameters) => throw new NotImplementedException();
}

Covariant declaration of IListingDTO<out T> allows you to use HListing ( which implements this interface) in service implementation

Upvotes: 1

Mehmet Karadeniz
Mehmet Karadeniz

Reputation: 110

This might change your design a little bit, but I'd try replacing the following code with something that doesn't take type parameter. In this way we can encapsulate ListingDTO into a class that doesn't take type parameter directly.

abstract class ListingItemBase : ListingItemDTO { }

abstract class ListingDTOBase : ListingDTO<ListingItemBase>
{
    public List<ListingItemBase> Items { get; set; }
}

And make HListing inherit ListingDTOBase:

class HListing : ListingDTOBase { }

After that you should be able to define interface without using V type parameter:

interface IListingToolsService<in TFilter, out U>
                            where TFilter : ListingParams
                            where U : ListingDTOBase
{
    int Count(TFilter parameters);

    U Get(TFilter parameters);
}

And service:

class HListingToolsService : IListingToolsService<ListingParams, HListing>
{
    public int Count(ListingParams parameters) => throw new NotImplementedException();
    public HListing Get(ListingParams parameters) => throw new NotImplementedException();
}

Upvotes: 1

Related Questions