C# yield Follow-up to the end. In my class

133 views Asked by At

I have class PlayList like below. I have Playlist instance favorite with song ID {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} I need execute this.

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

this return me songs with id 1, 2, 3, 4. Next I execute this code.

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

And I need return song with id 5, 6, 7. But I do not know how to do that. I need remember last returned item and next time start with next item. And if I execute this

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

I want return all song in playlist from last returned item (in this case 7) to end (8, 9 , 10). But this is not necessary.

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

Thank you for your advices!

2

There are 2 answers

0
nawfal On BEST ANSWER
  1. It's better not to go against convention here, that will be maintenance nightmare. I'll keep the expected behaviour of foreach. Instead you can have another overload to enumerate the rest of the songs.

  2. I would use Skip and Take for this purpose, ie in GetSong method, much simpler that way.

  3. GetSong is a poor name for a method that returns a sequence. I would rename it to GetSongs, but I prefer a more descriptive name like GetNext or just Next.

  4. To get the remaining songs, another overload makes more sense here. I'm a fan of optional arguments, but in this case I wouldn't want it.

So here we go

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

Call it like:

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
}
0
fejesjoco On

Use a plain old enumerator: call GetEnumerator of your list and then iterate with that. This is what foreach calls beneath the curtains. This is the construct which does exactly what you want: it lets you iterate one by one and remembers where you are in the iteration.