Josh
Josh

Reputation: 8477

C# Interface base usage

I have base Interface:

public interface IDebtBase
{
  // with properties
}

which is inherited by another interface

public interface IDebt : IDebtBase
{
  // with properties
}

Another interface defines the IDebt interface in a list:

public interface ILoanBase
{
     List<IDebtBase> Debts { get; set; }
}

which is inherited for another interface ILoan:

public interface ILoan : ILoanBase
{
  // with properties
}

The Loan object implements the ILoan:

public class Loan : ILoan
{
    List<IDebt> Debts { get; set; }
}

For full reference, the Debt object implements IDebt:

public class Debt : IDebt
{
     // with properties
}

I receive an error that says that List in Loan needs to be IDebtBase not IDebt:

'Loan' does not implement interface member 'ILoanBase.Debts'

Should this work, or does Interface inheritance not work this way?

Upvotes: 1

Views: 110

Answers (3)

Heinzi
Heinzi

Reputation: 172220

Let me explain why this won't work.

Assume I add another class Foo : IDebtBase and I execute

myLoan.Debts.Add(New Foo());

Adding a Foo works on a List<IDebtBase> (i.e., it works on ILoanBase.Debts), but not on a List<IDebt> (i.e., it won't work on Loan.Debts).

Thus, if the compiler allowed you to implement ILoanBase in Loan in the way you proposed, the Liskov substitution principle would be violated.

Upvotes: 1

John Alexiou
John Alexiou

Reputation: 29244

I think what you are trying to do is declare a list of interfaces but actually implement a list of concrete types. This is tricky to do with a static language like C#. You best choice is to use generics for the loan base interface.

public interface IDebtBase
{
    decimal Amount { get; set; }
}

public interface IDebt : IDebtBase
{
    int ID { get; set; }
}

public interface ILoanBase<TDebt> where TDebt : IDebtBase
{
    List<TDebt> Debts { get; set; }
}

public interface ILoan : ILoanBase<IDebt>
{
    decimal Rate { get; set; }
}

public class Debt : IDebt
{
    public int ID { get; set; }
    public decimal Amount { get; set; }
}

public class Loan : ILoan
{
    public static decimal DefaultRate=0.18m;
    public Loan()
    {
        this.Debts=new List<IDebt>();
        this.Rate=DefaultRate;
    }
    public List<IDebt> Debts { get; set; }
    public decimal Rate { get; set; }

    public void AddDebt(int id, decimal amount)
    {
        Debts.Add(new Debt() { ID=id, Amount=amount });
    }
}

class Program
{
    static void Main(string[] args)
    {
        var loan=new Loan() { Rate=0.22m };
        loan.AddDebt(1002, 5400m);
    }
}

The drawback is than not all loans derive from one type. That is because ILoadBase<IDebt> is different from ILoadBase<IDebtBase>. IN my experience building a hierarchy tree with interfaces is very tricky once you start having collections involved.

An alternative to the above is to change the loan type to use the concrete debt class

public interface ILoan : ILoanBase<Debt>  // change from ILoanBase<IDebt>
{
    decimal Rate { get; set; }
}


public class Loan : ILoan
{
    public static decimal DefaultRate=0.18m;
    public Loan()
    {
        this.Debts=new List<Debt>();  // change from List<IDebt>
        this.Rate=DefaultRate;
    }
    public List<Debt> Debts { get; set; } // Change from List<IDebt>
    public decimal Rate { get; set; }

    public void AddDebt(int id, decimal amount)
    {
        Debts.Add(new Debt() { ID=id, Amount=amount });
    }
}

which will give you in the end a List<Debt> collection (which I assume is what you desire). Now to make it more useful create a common base loan class without generics

public interface ILoanBase
{
    string Issuer { get; set; }
    IList<IDebtBase> GetDebts();
}

and use it for every loan interface. Finally in the concrete loan class you need to implement GetDebts() which would convert the List<Debt> into List<IDebtBase>.

public interface ILoanBase<TDebt> : ILoanBase
    where TDebt : IDebtBase
{
    List<TDebt> Debts { get; set; }
}
public class Loan : ILoan
{
    ...
    public IList<IDebtBase> GetDebts()
    {
        return Debts.OfType<IDebtBase>().ToList();
    }
}

now you have collect all the loans into a single collection

    static void Main(string[] args)
    {
        List<ILoanBase> loans=new List<ILoanBase>();
        //populate list
        var abc=new Loan() { Issuer="ABC", Rate=0.22m };
        abc.AddDebt(1002, 5400m);
        loans.Add(abc);

        //iterate list
        foreach (var loan in loans)
        {
            Console.WriteLine(loan.Issuer);
            foreach (var debt in loan.GetDebts())
            {
                Console.WriteLine(debt.Amount.ToString("C"));
            }
        }
        // ABC
        // $5,400.00
    }

Upvotes: 1

Paul Sullivan
Paul Sullivan

Reputation: 2875

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Loan l = new Loan();
            var a = 1;
        }
    }

    public interface IDebtBase
    {
        // with properties
    }

    public interface IDebt : IDebtBase
    {
        // with properties
    }

    public interface ILoanBase<T> where T : IDebtBase
    {
        List<T> Debts { get; set; }
    }

    public interface ILoan : ILoanBase<IDebt>
    {
        // with properties
    }

    public class Loan : ILoan
    {
        public List<IDebt> Debts { get; set; }
    }
}

Upvotes: 1

Related Questions