【问题标题】:C# Interface base usageC# 接口基本用法
【发布时间】:2016-12-17 17:03:14
【问题描述】:

我有基本接口:

public interface IDebtBase
{
  // with properties
}

被另一个接口继承

public interface IDebt : IDebtBase
{
  // with properties
}

另一个接口在列表中定义了 IDebt 接口:

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

为另一个接口ILoan继承:

public interface ILoan : ILoanBase
{
  // with properties
}

Loan 对象实现了 ILoan:

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

为了完整参考,Debt 对象实现了 IDebt:

public class Debt : IDebt
{
     // with properties
}

我收到一条错误消息,指出 Loan 中的 List 需要是 IDebtBase 而不是 IDebt:

'Loan' 没有实现接口成员'ILoanBase.Debts'

应该这样工作,还是接口继承不能这样工作?

【问题讨论】:

  • 使用泛型和泛型约束
  • 我应该把通用约束放在哪里,作为 ILoan 上列表的覆盖?
  • List&lt;IDebtBase&gt;List&lt;IDebt&gt; 是不同的类型。
  • 既然 IDebt 继承了 IDebtBase,List Debts 不应该在 Loan 类中工作吗?
  • 不,它没有。在 C# 中查找协变和逆变以获取一些示例。还请展示具体的Debt 类和实现IDebt

标签: c# oop interface


【解决方案1】:
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; }
    }
}

【讨论】:

  • 有趣,我试试看,我担心我使用的是实体框架,我会混淆它。
  • 不,它应该能够处理泛型(毕竟 IList 是基于泛型的对象...... IList
【解决方案2】:

我认为您要做的是声明一个接口列表,但实际上实现了一个具体类型列表。这对于像C# 这样的静态语言来说是很棘手的。您最好的选择是对贷款基础接口使用泛型。

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);
    }
}

缺点是并非所有贷款都来自一种类型。那是因为ILoadBase&lt;IDebt&gt;ILoadBase&lt;IDebtBase&gt; 不同。根据我的经验,一旦您开始涉及集合,使用接口构建层次结构树就非常棘手。

上述方法的替代方法是将贷款类型更改为使用具体的债务类别

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 });
    }
}

这最终会给你一个List&lt;Debt&gt; 集合(我认为这是你想要的)。现在为了让它更有用创建一个通用的基础贷款类没有泛型

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

并将其用于每个贷款接口。最后,在具体的贷款类中,您需要实现GetDebts(),它将List&lt;Debt&gt; 转换为List&lt;IDebtBase&gt;

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();
    }
}

现在您已将所有贷款收集到一个集合中

    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
    }

【讨论】:

  • 有趣的方法,你用“一旦你开始涉及集合,用接口构建一个层次结构树是非常棘手的”。我将用我的应用测试方法。
【解决方案3】:

让我解释一下为什么这不起作用。

假设我添加另一个类 Foo : IDebtBase 并执行

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

添加FooList&lt;IDebtBase&gt; 有效(即,它适用于ILoanBase.Debts),但不适用于List&lt;IDebt&gt;(即,它不适用于Loan.Debts)。

因此,如果编译器允许您按照您建议的方式在Loan 中实现ILoanBase,则将违反Liskov substitution principle

【讨论】:

  • 有道理,感谢您将其与 Liskov 替换原则联系起来。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-25
  • 1970-01-01
相关资源
最近更新 更多