【问题标题】:C# Inheritance of lists of interfacesC# 接口列表的继承
【发布时间】:2019-08-06 11:20:56
【问题描述】:

我对 C# 接口有点陌生,对于继承接口列表问题的扩展方法,我遇到了一个相当尴尬的解决方案。示例接口如下所示:

public interface IData
{
    int Value { get; set; }
}

public interface IDataWithName : IData
{
    string Name { get; set; }
}

public interface IDataContainer
{
    IList<IData> DataList { get; set; }
}

public interface IDataWithNameContainer : IDataContainer
{
    new IList<IDataWithName> DataList { get; set; }
}

使用扩展方法:

public static class ExtensionMethod
{
    public static int CountNumberOfIDataItems(this IDataContainer i)
    {
        return i.DataList.Count();
    }
}

实现这些接口时,IList DataList 和IList IDataContainer.DataList 都需要实现。虽然这是可能的,但生成的代码并不优雅:

public class DataNameImplimentatioFixed : IDataWithNameContainer
{
    public IList<IDataWithName> DataList { get; set; }
    IList<IData> IDataContainer.DataList
    {
        get => new List<IData>(DataList);
        set
        {
            DataList = new List<IDataWithName>();
            foreach (IDataWithName _dataLoop in value) { DataList.Add(_dataLoop); }
        }
    }
}

每次调用扩展时复制列表可能会导致一些性能问题,并且需要额外检查 IDataContainer 上的任何其他方法不会尝试添加从 IData 但不是从 IDataWithName 继承的类。

感觉应该有更好的方法来使用扩展方法,这样就不会那么容易受到未来扩展问题的影响。我能想到的最佳解决方案是将 IList DataList 从 IDataContainer 中取出,并为每个包含列表的类提供单独的扩展方法。

谁能想出比这更好的解决方案?

【问题讨论】:

  • 扩展方法不是接口或类的一部分,因此不适用继承。 您的 问题与 hiding DataList 在 IDataWithNameContainer 中有关。这与扩展方法无关,是在相关接口中使用相同成员名称的设计的问题
  • 这可以通过使两个容器接口通用并在 IDataWithNameContainer 中使用更严格的类型约束来解决
  • 关于扩展方法的好点只是问题出现的地方,而不是问题所在的位置。我会从标签和标题中删除它。

标签: c# inheritance interface


【解决方案1】:

问题在于IDataWithNameContainer 隐藏了它从IDataContainer 继承的DataList 属性,而不是扩展方法。它不应该这样做。

创建仅接受特定 IData 实现的派生容器类的简单方法是使容器接口通用并在 IDataWithNameContainer 中使用更严格的类型约束:

public interface IData
{
    int Value { get; set; }
}

public interface IDataWithName : IData
{
    string Name { get; set; }
}

public interface IDataContainer<T> where T:IData
{
    IList<T> DataList { get; set; }
}

public interface IDataWithNameContainer<T> : IDataContainer<T> where T:IDataWithName
{

}

public static class ExtensionMethod
{
    public static int CountNumberOfIDataItems<T>(this IDataContainer<T> i) where T:IData
    {
        return i.DataList.Count;
    }
}

以这种方式创建命名容器很容易:

public class Boo:IDataWithName 
{
    public int Value { get; set; }
    public string Name { get; set; }
}


public class BooContainer: IDataWithNameContainer<Boo>
{
    public IList<Boo> DataList { get; set; }
}

更新

顺便说一句,没有理由在循环中向DataList 添加新项目。 List 构造函数可以接受具有初始值的 IEnumerable:

public class BooContainer: IDataWithNameContainer<Boo>
{
    IList<Boo> _list=new List<Boo>();
    public IList<Boo> DataList 
    { 
        get => _list; 
        set => _list=new List<Boo>(value); 
    }
}

这允许通过将列表和数组的内容复制到新列表中来将它们存储到DataList

如果有人设置了 null,这将抛出。为避免这种情况,可以在 setter 中使用 null 替换运算符:

    set => _list=new List<Boo>(value ?? new Boo[0]); 

【讨论】:

  • 我想这就是我所追求的!它隐藏了问题所在的列表,并且在我遇到此问题的代码中输入容器接口将很容易。谢谢!
【解决方案2】:

您不需要IDataContainerIDataWithNameContainer。你已经有了IDataWithName 接口中的值,那么为什么IDataWithNameContainer 也继承自IDataContainer?这不是你制作额外界面的原因吗?

IData
IDataWithName : IData
IList<IDataWithName>  //This already has a list of (name) and (value)

简单地让你的班级像这样:

public interface IDataWithNameContainer // Do not add this here -> : IDataContainer
{
    IList<IDataWithName> DataList { get; set; }
}

【讨论】:

  • IDataWithNameContainer 继承自 IDataContainer 可能与 IList 继承自 IEnumerable 的原因相同。因为它们指的是不同的事物(行为、内容)。在这种情况下,“不同”意味着它们应该包含实现特定接口的项目,可能是因为容器依赖于它们。
  • 无论如何,new IList&lt;IDataWithName&gt; DataList { get; set; } 都会引发编译器警告
  • @PanagiotisKanavos 这不是我想要的。一个如果接口用于值,另一个用于名称和值。为什么他想要两个名称相同、接口相同的值列表?
  • @PanagiotisKanavos 你是对的。我不打算将new 留在修复程序中。我已经更新了。
猜你喜欢
  • 2011-03-24
  • 1970-01-01
  • 2023-03-14
  • 2017-07-23
  • 2011-07-31
  • 1970-01-01
  • 2011-04-21
  • 2023-03-04
相关资源
最近更新 更多