Search code examples
c#recursionmp3invalidoperationexceptionid3-tag

C# Issue diagnosting InvalidOperationException


I am writing a small application to modify ID3-Tags of Audio Files. The UI is very simple: You choose your media library and can apply various code logic recursively to all files.

I am using TagLib to read and write Tags in my Project.

In order to work with the library, I need to read all Tags and store them in a List of Albums.

The Problem: In the function private void GenerateClassifiedLibrary(List files) is an InvalidOperationException thrown and I can't find any reason, why this could happen.

Here is the source code of the main application:

using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;

namespace Music_Metadata_Experiments
{
    public partial class Form1 : Form
    {
        // Global Variables Declaration
        public static List<Album> classifiedLibrary = new List<Album>();
        // classifiedLibrary is a List of Albums
        // Albums contain Songs
        // Songs contain Tags like "TITLE", "ARTIST", "ALBUM", "GENRE", etc. which can be read and displayed by every Media Player.

        // Start up
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            txtLibraryPath.Text = Environment.GetEnvironmentVariable("USERPROFILE") + "\\Music";
        }

        private void BtnSelectLibrary_Click(object sender, EventArgs e)
        {
            String libraryPath = "";
            List<String> musicFiles;

            if (libraryBrowserDialog.ShowDialog() == DialogResult.OK)
            {
                libraryPath = libraryBrowserDialog.SelectedPath;
            }

            if (libraryPath != null && libraryPath != "")
            {
                txtLibraryPath.Text = libraryPath;
                Log("New Library: " + libraryPath);
            }

            MessageBox.Show("Mediathek wird eingelesen. Dieser Vorgang kann je nach Größe sehr lange dauern.\nDrücke Strg+Shift+Esc für mehr technische Infos.\n\nBitte warten!");

            // Reading the Library:
            musicFiles = GetFilesFromLibrary(libraryPath);
            GenerateClassifiedLibrary(musicFiles);
        }

        // Adjustable Code
        private void ExecuteLogic1_Click(object sender, EventArgs e)
        { // Not implemented yet
            // Goal: Fix this problem -> Song 1 from DISKNUMBER 1 and Song 1 from DISKNUMBER 2 are shown underneath each other, because DISKNUMBER is missing or corrupted.
            // Function: Finds multiple Songs having TITLENUMBER == 1 but are in the same ALBUM and have the same DISKNUMBER or no DISKNUMBER
            Log("Executing Logic 1");

            Log("Library Count: " + classifiedLibrary.Count);
            foreach (Album album in classifiedLibrary)
            {
                Log("Album: " + album.Name);
            }
        }

        private void ExecuteLogic2_Click(object sender, EventArgs e)
        {

        }

        private void ExecuteLogic3_Click(object sender, EventArgs e)
        {

        }

        private void ExecuteLogic4_Click(object sender, EventArgs e)
        {

        }

        private void ExecuteLogic5_Click(object sender, EventArgs e)
        {

        }

        private void ExecuteLogic6_Click(object sender, EventArgs e)
        {

        }

        private void ExecuteLogic7_Click(object sender, EventArgs e)
        {

        }

        private void ExecuteLogic8_Click(object sender, EventArgs e)
        {

        }

        // Helper Methods
        private List<String> GetFilesFromLibrary(string libraryPath)
        {
            String currentFileName = null;
            String fileExtensionToScanFor = "mp3";
            bool skipOnError = true;
            List<String> files = new List<String>();

            try
            {
                foreach (string file in Directory.EnumerateFiles(libraryPath, "*." + fileExtensionToScanFor, SearchOption.AllDirectories))
                {
                    currentFileName = file;
                    files.Add(file);
                }
                Log("Found " + files.Count + " Files in Library");
            }
            catch (Exception any)
            {
                Console.WriteLine("Erroar reading file: " + currentFileName + " // Problem: " + any.Message);

                if (skipOnError == false)
                {
                    Application.Exit();
                }
            }
            return files;
        }

        private void GenerateClassifiedLibrary(List<String> files)
        {
            Log("Generating a Library to work with");
            TagLib.File currentFile = null;

            foreach (string song in files) // ALL Songs found in Library Folder
            {
                try
                {
                    currentFile = TagLib.File.Create(song);

                    String trackAlbum = currentFile.Tag.Album;

                    // Add at least 1 Album to classifiedLibrary
                    if (classifiedLibrary.Count < 1)
                    {
                        List<TagLib.File> albumTrackList = new List<TagLib.File>();
                        albumTrackList.Add(currentFile);
                        Album firstAlbum = new Album(trackAlbum, albumTrackList);

                        classifiedLibrary.Add(firstAlbum);
                    }

                    // Find an Album in classifiedLibrary to which this track belongs to
                    foreach (Album album in classifiedLibrary) // FIXME: InvalidOperationException thrown HERE
                    {
                        Console.WriteLine("Album: " + album.Name);
                        Console.WriteLine("Track: " + currentFile.Tag.Title); // not matching to album above

                        if (album.Name == trackAlbum)
                        {
                            // Album found in classifiedLibrary
                            Log("EXISTING Album: " + trackAlbum + " | Track Count = " + album.Tracks.Count);

                            // Now checking if the track exists or add it
                            List<TagLib.File> tracks = album.Tracks;

                            foreach (TagLib.File track in tracks)
                            {
                                if (track == currentFile)
                                {
                                    // Already added to this Album!
                                    Log("Already added: " + track.Tag.Title);
                                }
                                else
                                {
                                    if (track.Tag.Album == currentFile.Tag.Album)
                                    {
                                        tracks.Add(track);
                                        Log("X Added Track '" + track.Tag.Title + "' to '" + album.Name + "'.");
                                    }
                                    else
                                    {
                                        // Next one.
                                    }
                                    // Next one.
                                }
                                // Next one.
                            }
                            album.Tracks = tracks;
                        }
                        else
                        {
                            // Album not found in classifiedLibrary - FIXME: Album existiert und wird trotzdem neu erstellt
                            Log("NEW Album: " + trackAlbum);

                            List<TagLib.File> trackOrigin = new List<TagLib.File>();
                            Album newAlbum = new Album(trackAlbum, trackOrigin);
                            newAlbum.Tracks.Add(currentFile);
                            Log("Y Added Track '" + currentFile.Tag.Title + "' to '" + newAlbum.Name + "'.");

                            classifiedLibrary.Add(newAlbum);

                            Console.WriteLine("SUCCESS 1");
                        }
                        Console.WriteLine("SUCCESS 2");
                    } // ------------------------------ Exception
                    Console.WriteLine("SUCCESS 3");
                }
                catch (Exception any)
                {
                    Console.WriteLine("------------------------------------------");
                    Console.WriteLine("GenerateClassifiedLibrary: A more fatal Erroar reading file: " + currentFile.Name);
                    Console.WriteLine("Exception: " + any.ToString());
                    Console.WriteLine("------------------------------------------");
                }
            }

            // Generating classifiedLibrary done.
            Log("Generated Library Contents: " + classifiedLibrary.Count + " Albums in classifiedLibrary");
        }

        private void Log(String line)
        {
            RtbLogs.Text += "\n" + line;
        }

    }
}

Here the source of the Album object:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Music_Metadata_Experiments
{
    public class Album
    {
        public String Name
        {
            get; set;
        }

        public List<TagLib.File> Tracks
        {
            get; set;
        }

        public Album(String name, List<TagLib.File> tracks)
        {
            Name = name;
            Tracks = tracks;
        }

        public bool Contains(String trackTitle)
        {
            try
            {
                foreach (TagLib.File track in Tracks)
                {
                    if (track.Tag.Title == trackTitle)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("------------------------------------------");
                Console.WriteLine("Exception occured while checking Album.Contains(" + trackTitle + ")");
                Console.WriteLine(e.Message);
                Console.WriteLine("------------------------------------------");
                return false;
            }
            return false;
        }
    }
}

Example of the Console Output:

Album: Holz (Weihnachtslied) - Single
Track: Curse My Name
SUCCESS 1
SUCCESS 2
Ausnahme ausgelöst: "System.InvalidOperationException" in mscorlib.dll
------------------------------------------
GenerateClassifiedLibrary: A more fatal Erroar reading file: C:\Users\XXX\Music\iTunes\iTunes Media\Music\Blind Guardian\At the Edge of Time\1-05 Curse My Name.mp3
Exception: System.InvalidOperationException: Die Sammlung wurde geändert. Der Enumerationsvorgang kann möglicherweise nicht ausgeführt werden.
   bei System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   bei System.Collections.Generic.List`1.Enumerator.MoveNextRare()
   bei System.Collections.Generic.List`1.Enumerator.MoveNext()
   bei Music_Metadata_Experiments.Form1.GenerateClassifiedLibrary(List`1 files) in C:\Users\XXX\Documents\Projekte\Music Metadata Experiments\Music Metadata Experiments\Form1.cs:Zeile 152.
------------------------------------------

A Screenshot of what's happening.

Btw: I am new to C#

--

Edit: It's now working as expected. I've also set the Application to x64 to enable the Processing of larger Music Libraries.

Updated Project: https://1drv.ms/u/s!AphAKlhyqhP4g2eEk-3aqLeifo89?e=ACkb6w


Solution

  • So there error is key here: Exception: System.InvalidOperationException: Die Sammlung wurde geändert. Der Enumerationsvorgang kann möglicherweise nicht ausgeführt werden.

    In English roughly translates to the following error message:

    System.InvalidOperationException: Collection was modified; enumeration operation may not execute

    This means you are looping through a list, but you are modifying that list (e.g. adding or removing items) while you are looping through it.

    Sample:

    var lines = new List<string>();
    lines.Add("hello");
    lines.Add("world");
    foreach(var line in lines) 
    {
        lines.Add("adding a new item"); // Invalid operation
    }
    

    It's an invalid operation to add / remove an item from the list while you are looping through it.

    In your case this is the culprit:

    classifiedLibrary.Add(newAlbum); // << This is not valid inside a loop.
    Console.WriteLine("SUCCESS 1");
    

    How to fix this? Simple - create an extra list that you add items to when they match a certain criteria, and don't use the list you are currently looping through.

    Other tip:

    I strongly advise to set your windows language to english. This will help you (as there are far more resources available on stackoverflow with the right english translation).