【问题标题】:C# yield Follow-up to the end. In my classC# yield 跟进到最后。在我的课上
【发布时间】:2013-12-24 14:05:56
【问题描述】:

我有如下的播放列表类。我有歌曲 ID 为 {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 的播放列表实例收藏,我需要执行此操作。

foreach(Song song in favorite.GetSong(4))
{
    //Somthing
}

这将返回 ID 为 1、2、3、4 的歌曲。接下来我执行此代码。

foreach(Song song in favorite.GetSong(3))
{
    //Somthing
}

我需要返回 id 为 5、6、7 的歌曲。但我不知道该怎么做。我需要记住上次退回的物品,下次从下一件物品开始。如果我执行这个

foreach(Song song in favorite)
{
    //Somthing
}

我想返回播放列表中从最后返回的项目(在本例中为 7)到结束(8、9、10)的所有歌曲。但这不是必需的。

internal class PlayList : IEnumerable<SongID>
{
    private List<SongID> songsInAlbum = new List<SongID>();

    public Song this[SongID id]
    {
        get
        {
            if (songsInAlbum.Contains(id))
            {
                return AllSongs[id];
            }
            throw new KeyNotFoundException();
        }
    }

    public IEnumerable<Song> GetSong(int maxReturn = Int32.MaxValue)
    {
        int wasReturned = 0;
        foreach (SongID id in songsInAlbum)
        {
            if (wasReturned < maxReturn)
            {
                yield return AllSong[id];
                wasReturned++;
            }
        }
    }

    public void AddSong(SongID id)
    {
        songsInAlbum.Add(id);
    }

    public IEnumerator<SongID> GetEnumerator()
    {
        return songsInAlbum.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

感谢您的建议!

【问题讨论】:

  • 您的要求与foreach 的一般行为背道而驰,在该行为中迭代某些内容不会改变其状态。虽然可以实现这一点,但最好将您的设计修改为更符合习惯。
  • @JonSkeet 如果 GetSong 返回一个新的 IEnumerable 并且他没有在访问器中让步,这仍然是错误的吗?
  • @Dan:返回一个集合来修改对象状态的方法很奇怪,IMO。我意识到这与我之前的评论不太一样,请注意……我仍然觉得有点奇怪。
  • @JonSkeet 啊,完全同意。正如我所说(在编辑之前,抱歉),我忽略了他的内部实现并判断提议的接口。
  • 听起来你在排队。

标签: c# yield yield-keyword


【解决方案1】:
  1. 这里最好不要违背惯例,那将是维护的噩梦。我将保持foreach 的预期行为。相反,您可以有另一个重载来枚举其余的歌曲

  2. 为此,我会使用SkipTake,即在GetSong 方法中,这样更简单。

  3. GetSong 是返回序列的方法的糟糕名称。我会将其重命名为 GetSongs,但我更喜欢更具描述性的名称,例如 GetNext 或只是 Next

  4. 为了获得剩余的歌曲,这里另一个重载更有意义。我是可选参数的粉丝,但在这种情况下我不想要它。

所以我们开始

internal class PlayList : IEnumerable<SongID>
{
    private List<SongID> songsInAlbum = new List<SongID>();
    int currentIndex = 0; //maintain an index per instance; 1-based

    int Count //make it public if it makes sense
    {
        get { return songsInAlbum.Count; }
    }

    public Song this[SongID id]
    {
        get
        {
            if (songsInAlbum.Contains(id))
            {
                return AllSongs[id];
            }
            throw new KeyNotFoundException();
        }
    }

    public IEnumerable<Song> Next(int noOfSongs)
    {
        try 
        {
            return this.Skip(currentIndex).Take(noOfSongs).Select(x => AllSong[x]);
        }
        finally
        {
            if (currentIndex < Count)
                currentIndex += Math.Min(Count - currentIndex, noOfSongs);
        }
    }

    public IEnumerable<Song> Next() //or 'Rest', sounds good.
    {
        return Next(int.MaxValue); //less readable
        //or
        return Next(Count); //a more meaningful number
        //or
        return Next(Count - currentIndex); //for correctness
    }

    public void AddSong(SongID id)
    {
        songsInAlbum.Add(id);
    }

    public IEnumerator<SongID> GetEnumerator() //general enumerator, enumerates them all
    {
        return songsInAlbum.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

这样称呼:

foreach(Song song in favorite.Next(4))
{
    //1, 2, 3, 4
}

foreach(Song song in favorite.Next(3))
{
    //5, 6, 7
}

foreach(Song song in favourite.Next())
{
    //8, 9, 10
}

foreach(Song song in favourite)
{
    //1, 2, ... 10
}

【讨论】:

    【解决方案2】:

    使用普通的旧枚举器:调用列表的 GetEnumerator,然后对其进行迭代。这就是 foreach 在窗帘下所说的。这正是你想要的结构:它让你一个接一个地迭代并记住你在迭代中的位置。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-11
      • 2012-09-30
      • 2012-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-14
      相关资源
      最近更新 更多