Search code examples
c#interfacebase-class

Inherit a base class and an interface same time


In this course project, the teacher created an abstract base class (EfEntityRepositoryBase) for data access, a concrete class for each entity (ProductDal) that inherits abstract base class and implements an interface (IEntityRepository). ProductDal also has its interface (IProductDal) which also implements IEntityRepository.

What is the use case for doing it? I can't understand the point of IProductDal implementing IEntityRepository, since ProductDal already inherits the abstract base class that implements the same interface. So if any function updates in IEntityRepository, should be no problem. If someone can explain so would be great. Below abstract class and interfaces code.

public class ProductDal : EfEntityRepositoryBase<Product>, IProductDal{ }

public interface IEntityRepository<T>
{
    void Add(T entity);
    void Delete(T entity);
    void Update(T entity);
    List<T> GetAll(Expression<Func<T, bool>> expression = null);
    T GetById(Expression<Func<T, bool>> expression);
}

public interface IProductDal: IEntityRepository<Product>
{
}

public class EfEntityRepositoryBase<TEntity> : IEntityRepository<TEntity> where TEntity : class, IEntity, new()
{
    public void Add(TEntity entity)
    {
        using (BookStoreTrackerDBContext context = new BookStoreTrackerDBContext())
        {
            var addedEntity = context.Entry(entity);
            addedEntity.State = EntityState.Added;
            context.SaveChanges();
        }
    }
}

Solution

  • I think it is easy to understand that you feel, when looking at your provided example, tempted to call out the IProductDal interface as being redundant. In fact, it doesn't add any extra members to the type ProductDal because the interface IProductDal and the generic class EfEntityRepositoryBase are defined with the same generic parameter type Product. Since those teaching examples are not set in context of real application code, their real intentions or ideas behind them are not easy to understand.


    As a side note, you should know that in case the class EfEntityRepositoryBase<TEntity> would be defined with a different generic parameter type than Product e.g., int, ProductDal would have two implementations/member overloads of the IEntityRepository<T> interface. For example:

    public class ProductDal : EfEntityRepositoryBase<int>, IProductDal
    { 
      // Implementation of IProductDal. The EfEntityRepositoryBase type provides another 'int' overload
      public void Add(Product entity) {}
    }
    
    void Main()
    {
      var productDal = new ProductDal();
      
      // Implementation of IEntityRepository<int> provided by class EfEntityRepositoryBase<int>
      productDal.Add(6);
    
      // Implementation of 'IProductDal' (provided by class 'ProductDal')  
      productDal.Add(new Product());
    }
    

    You can see that your provided example shows a special case where the EfEntityRepositoryBase<TEntity> already provides the implementation for the IEntityRepository<Product> and the IProductDal interfaces.


    Back to your example: if you make use of type casting, you will find another use case for having the alledgedly redundant type definitions:

    Given is your ProductDal type with the following class signature

    public class ProductDal : EfEntityRepositoryBase<int>, IProductDal
    

    You have now multiple types available to access the implementation of IEntityRepository<Product>

    void Main()
    {
      // Create an instance of ProducDal
      ProductDal productDal = new ProductDal();
    
      /* Use the instance of ProductDal with multiple overloads 
         to show implicit type casting */
      UseProductDal(productDal);
      UseIProductDal(productDal);
      UseIEntityRepository(productDal);
      UseEntityRepository(productDal);
    }
    
    void UseProductDal(ProductDal productDal)
    {
      // Instantiate the argument
      var product = new Product(); 
      productDal.Add(product);
    }
    
    void UseIProductDal(IProductDal productDal)
    {
      // Instantiate the argument
      var product = new Product(); 
      productDal.Add(product);
    }
    
    void UseIEntityRepository(IEntityRepository<Product> productDal)
    {
      // Instantiate the argument
      var product = new Product(); 
    
      productDal.Add(product);
    }
    
    void UseEntityRepositoryBase(EntityRepositoryBase<Product> productDal)
    {
      // Instantiate the argument
      var product = new Product(); 
      productDal.Add(product);
    }
    

    This shows how to make use of the implicit type casting and also how to use interfaces.
    You now see that although EntityRepositoryBase<Product> already implements IEntityRepository<Product>, still having ProductDal additionally implement the IProductDal interface makes perfect sense in order to enable ProductDal to be used where only the IProductDal interface is known.


    You can make use of interface casting to hide members. For example if you add exclusive members to each interface then this members are only accessible when casting the implementor to the corresponding interface:

    public interface IEntityRepository<T>
    {
      void Add(T entity);  
    }
    
    public interface IProductDal: IEntityRepository<Product>
    {
      // Exclusive member. Will be only visible when accessed through this interface. 
      int GetProductCount();
    }
    

    Given is your ProductDal type with the following class signature

    public class ProductDal : IEfEntityRepository<int>, IProductDal
    
    void Main()
    {
      // Create an instance of ProducDal
      ProductDal productDal = new ProductDal();
    
      /* Use the instance of ProductDal with multiple overloads 
         to show implicit type casting */
      UseProductDal(productDal);
      UseIProductDal(productDal);
      UseIEntityRepository(productDal);
      UseEntityRepository(productDal);
    }
    
    // All implemented interfaces are visible since there is no casting involved.
    // All members are referenced via the implementor type ProductDal.
    void UseProductDal(ProductDal productDal)
    {
      // Instantiate the argument
      var product = new Product(); 
      
      productDal.Add(product);
      int productCount = productDal.getProductCount();
    }
    
    // Only 'IProductDal' is visible since there is an implicit cast to an interface type involved
    void UseIProductDal(IProductDal productDal)
    {
      // Instantiate the argument
      var product = new Product(); 
    
      // 'Add()' is provided by 'IEntityRepository<T>', 
      // which is implemented by 'IProductDal' and therefore "visible"
      productDal.Add(product); 
    
      // 'GetProductCount()' is provided by 'IProductDal'
      int productCount = productDal.GetProductCount();
    }
    
    // Only 'IEntityRepository<T>' is visible since there is an implicit cast to the interface type 
    void UseIEntityRepository(IEntityRepository<Product> productDal)
    {
      // Instantiate the argument
      var product = new Product(); 
    
      productDal.Add(product);
    
      // 'GetProductCount()' is only available via the 'IProductDal' interface. 
      // It's not visible here.
      //int productCount = productDal.GetProductCount();
    }