Search code examples
c#genericsinterface.net-coreasp.net-core-mvc-2.0

c# 7 when using generics for method parameter I get The constraints for type parameter 'U' of method must match the constraints for the interface


I am trying to create an interface and a concrete implementation where the Interface is a generic type and one of the methods has a generic parameter.

I want to keep the GetPagedList method parameter, resourceParams, generic so I can pass in different resourceParams objects for different implementations of the Interface.

When using the code shown below, I am getting the error;

The constraints for type parameter 'U' of method 'ShippingServicesRepository.GetPagedList(U)' must match the constraints for the type parameter 'U' of interface method IBaseRepository.GetPagedList(U). Consider using an explicit interface implementation instead

Here is my interface;

public interface IBaseRepository<T> 
{
    bool Save();
    bool Exists(int recordId);
    bool MarkForDeletion(int recordId);
    PagedList<T> GetPagedList<U>(U resourceParams) where U : class;
    T Get(int id);
    void Add(T record);
    void Update(T record);
}

And here is my implementation;

public class ShippingServicesRepository<T> : IBaseRepository<T> 
{

    //                      /--- GetPagedList is what is throwing the error
    //                      |
    public PagedList<T> GetPagedList<U> (U resourceParams) where U : ShippingServicesResourceParameters
    {
        try
        {

            var collectionBeforePaging =
                _manifestContext.ShippingServices
                .ApplySort(resourceParams.OrderBy, _propertyMappingService.GetPropertyMapping<ShippingServicesDto, ShippingServices>());
            if (!string.IsNullOrEmpty(resourceParams.SearchQuery))
            {
                var searchQueryForWhereClause = resourceParams.SearchQuery.Trim().ToLowerInvariant();
                collectionBeforePaging = collectionBeforePaging
                    .Where(a => a.ReferenceId.ToLowerInvariant().Contains(searchQueryForWhereClause));
            }
            collectionBeforePaging = collectionBeforePaging
                .Where(d => d.DeleteFlag == resourceParams.DeleteFlag);

            return (dynamic)PagedList<ShippingServices>.Create(collectionBeforePaging,
                resourceParams.PageNumber,
                resourceParams.PageSize);
        }
        catch (Exception)
        {
            _logger.LogError(500, "ShippingServices Filter [{FILTER}]", resourceParams);
            throw;
        }
    }

    public void Add(T record)
    {
        ...
    }

    public bool Exists(int recordId)
    {
        ...
    }

    public T Get(int id)
    {
        ...
    }

    public bool MarkForDeletion(int recordId)
    {
        ...
    }

    public bool Save()
    {
        ...
    }

    public void Update(T record)
    {
        ...
    }

}

Here is my ShippingServicesResourceParameters class

public class ShippingServicesResourceParameters : BaseResourceParameters
{
    public string FileName { get; set; }
}

Here is the BaseResourceParameters class inherited by ShippingServicesResourceParameters

public class BaseResourceParameters
{
    private int _pageSize;
    public int PageNumber { get; set; } = 1;
    public int PageSize
    {
        get
        {
            return _pageSize;
        }
        set
        {
            _pageSize = (value > MaxPageSize) ? MaxPageSize : value;
            if (value == 0)
            {
                _pageSize = 10; // set a default size
            }
        }
    }

    public int MaxPageSize { get; set; } = 20;
    public bool DeleteFlag { get; set; }
    public string SearchQuery { get; set; }
    public string OrderBy { get; set; } = "Id";
    public string Fields { get; set; }
}

It I don't add the "where U : ShippingServicesResourceParameters" to the method signature in the concrete implementation and "where U : class" in the Interface, I get a "Cannot convert from Method Group to String..." error from the first use of the resourceParams variable in the concrete implementation. (at ".ApplySort(resourceParams.OrderBy")

What am I missing here?


Solution

  • Let's do what you should have done in the first place and make a minimal program that demonstrates the problem:

    interface I 
    {
        void M<U>(U u) where U : class;
    }
    class D 
    {
        public void O() {}
    }
    class C : I
    {
        public void M<U>(U u) where U : D
        {
            u.O();
        }
    }
    

    This is an error because C does not implement I. It does not implement I because:

    I i = new C();
    i.M<Giraffe>(new Giraffe());
    

    Now we have a giraffe passed to C.M<Giraffe>(Giraffe) but C.M<U> requires that U is D. So that's illegal.

    We cannot fix it like this:

        public void M<U>(U u) where U : class
        {
            u.O();
        }
    

    because now we could have D.O() called on a receiver of type Giraffe.

    Therefore we have to fix it like this:

    interface I 
    {
        void M<U>(U u) where U : D;
    }
    class D 
    {
        public void O() {}
    }
    class C : I
    {
        public void M<U>(U u) where U : D
        {
            u.O();
        }
    }
    

    And now we're all good.

    You are required to make the implementation constraints match the interface constraints, just like you're required to meet every other requirement imposed by the interface. Interfaces are contracts. You have to fulfill your end of the bargain.

    I note that this is what the error message says: you have to match the constraints, and you're not doing so. Pay attention to the error messages; most of the time they tell you what is wrong.