【问题标题】:Manipulating a class that derives from a List or Collection操作从 List 或 Collection 派生的类
【发布时间】:2020-10-14 18:04:06
【问题描述】:

我试图通过将列表“合并”到一个新类中来使其对用户更加友好,但是从该新类访问列表的特定功能和属性给我带来了麻烦。

这是一个例子:

public class Words : List<Word>
{
    public uint BytesPerWord { get; protected set; }

    public Word this[int index] => this.ElementAt(index);
  
    /*  public new Words GetRange(int index, int count)
    {
        var a = this.GetRange(index, count);

        return a;
    }*/

    public Words(uint bytesPerWord) : base()
    {
        BytesPerWord = bytesPerWord;
    }
}

现在,我尝试从其他地方将 someOtherWords 的“Words”类型的一部分提取到 myWords 中:

Words myWords = someOtherWords.GetRange((int)X, (int)Y);

这里有几个问题:

  • GetRange 无法访问。因此,我尝试创建它的新版本(上面已注释掉),但是“this.GetRange()”会创建 stackOverflow 异常。
  • “Word this[int index]”也抱怨隐藏的继承成员 - 但我猜这与之前的问题有关

非常感谢任何有关如何解决此问题的建议。
请注意,我也尝试用 Collection 替换 List,因为我已经阅读过我不应该从列表中派生,但这没有任何区别。

【问题讨论】:

  • 为什么要继承List&lt;T&gt;,重新定义GetRange?这是公共方法
  • @PavelAnikhouski,Words 应该更加用户友好。在我的代码库中引用单词比总是键入 List = new List() 更容易。我不必重新定义GetRange,我只想使用它,但它在我的示例中不起作用。

标签: c# list inheritance collections


【解决方案1】:

根据经验,您不应该从List&lt;T&gt; 派生;它有很多你可能不想继承的行为。

为避免重新发明轮子,可以通过组合使用List&lt;T&gt;

public class Words
{
    private readonly List<Word> _list; // Composition

    public uint BytesPerWord { get; protected set; }
    public Word this[int index] => _list[index];
  
    public Words GetRange(int index, int count)
    {
        var a = _list.GetRange(index, count);
        return new Words(BytesPerWord, a);
    }

    public Words(uint bytesPerWord) : this(bytesPerWord, new List<Word>()) { }

    private Words(uint bytesPerWord, List<Word> list)
    {
        BytesPerWord = bytesPerWord;
        _list = list;
    }
}

如果你需要你的类是可枚举的,你甚至可以实现IEnumerable

public class Words : IEnumerable<Word>
{
    private readonly List<Word> _list;

    //...

    public IEnumerator<Word> GetEnumerator() => _list.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

这同样适用于其他集合接口。

【讨论】:

  • 非常感谢。但在这种情况下,我不能做这样的事情:Words myWords = someOtherWords.GetRange((int)x, (int)y);
  • @stackMeUp 不,它应该调用私有构造函数,它被重载以接受列表。
  • 是的,因为错字没有工作 :-) List 应该是 List。我将无法再执行 Words.Foreach() 并且必须实现一些其他接口。
  • @stackMeUp 当然可以:public void ForEach (Action&lt;Word&gt; action) =&gt; _list.ForEach(action);
【解决方案2】:

您的代码无法运行,因为在 GetRange 方法中您调用了 this.GetRange(而不是 base.GetRange),这会创建一个无限循环,直到发生 stackoverflow 异常。

所以你可以把代码改成

public class Words : List<Word>
{
    public uint BytesPerWord { get; protected set; }
    public new Word this[int index] => this.ElementAt(index);

    public new Words GetRange(int index, int count)
    {
        var a = base.GetRange(index, count);
        Words copy = new Words(100);
        copy.AddRange(a);
        return copy;
    }

但是我有点困惑这将如何帮助您的代码的可读性。如果您不需要向List&lt;T&gt; 添加任何新功能,那么我认为隐藏变量的真实性质不是一个好主意。

还要注意这一行:

public Word this[int index] => this.ElementAt(index);

可以写在 new 修饰符之前,并且可以选择引用基类

public new Word this[int index] => base[index];

反对这个类有用的另一个原因。

【讨论】:

  • 这非常有用,谢谢。听起来这种清理的想法不是一个好主意。所以你宁愿总是暴露真名?但是你总是必须使用“ElementAt()”来访问 List,这比使用括号更麻烦,并且会降低代码的可读性?编辑:对不起,看来我仍然可以使用 [ ]
  • 确切地说,给变量起一个有意义的名称比定义一个与内置类型完全相同的新类型更重要。如果有人需要阅读您的代码,它会感到困惑,并且可能会浪费时间尝试理解不存在的差异。
  • 这很有趣,因为这是我的前任最初提出的一个概念,他有 10 多年的 C# 经验,并认为我正在向最好的人学习 :-( 我一年多前开始使用 C#,呵呵!
  • 我们当然是在意见领域,创建一个从框架已经定义的类型派生的新类型是完全正确的。但是,对我来说,如果您不需要向新类型添加某些内容,那么您就不需要创建新类型。对于工作,我经常需要对旧程序进行维护,这种习惯非常普遍,而且总是让我感到厌烦。
  • 哈哈哈,听起来这种方法背后有历史:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-09
  • 1970-01-01
  • 1970-01-01
  • 2012-10-15
  • 2018-08-13
相关资源
最近更新 更多