Search code examples
c#arraysobjectnullreferenceexception

Problem sorting objects based on value of its properties


I'm sitting here with a school project I can't seem to figure out; I am to create a console application that let's the user enter a number of salesmen for a hypothetical company. The information about the salesmen includes their name, district and number of sold items. The salesmen are then to be sorted into different levels according to the number of sold items. Finally, the salesmen are to be printed to the console. The printing should be done one level at the time, if for instance two salesmen reached level one and one reached level 2, the console should look something like:

John Johnsson, someDistrict, 33 items sold Mary Mara, someOtherDistrict, 40 items sold 2 salesmen reached level 1

Judy Juggernut, anotherDistrict, 67 items sold 1 salesmen reached level 2

And it's the printing part in question that gives me trouble. When the user enters information a new object of a salesman-class is created and stored in an array of salesmen. The number of items sold for each salesman is then checked and each salesman is assigned a level. The array is then sorted using bubblesort, to have the salesman with the least amount of sales on salesmanArray[0] and so on.

Everything works fine until its time to print the results to the console. I tried to write a method for it:

    public static void sortering(Salesman[] salesmenArray)
    {
        Salesman[] level1 = new Salesman[salesmenArray.Length];
        Salesman[] level2 = new Salesman[salesmenArray.Length];
        Salesman[] level3 = new Salesman[salesmenArray.Length];
        Salesman[] level4 = new Salesman[salesmenArray.Length];

        for (int i = 0; i < salesmenArray.Length - 1; i++)
        {
            if (salesmenArray[i].level == 1)
            {
                level1[i] = salesmenArray[i];

            } else if (salesmenArray[i].level == 2)
            {
                level2[i] = salesmenArray[i];

            } else if (salesmenArray[i].level == 3)
            {
                level3[i] = salesmenArray[i];

            } else if (salesmenArray[i].level == 4)
            {
                level4[i] = salesmenArray[i];
            }
        }

        if (level1.Length != 0)
        {
            for (int i = 0; i < level1.Length - 1; i++)
            {
                Console.WriteLine("Name: " + level1[i].name);
                Console.WriteLine("District: " + level1[i].district);
                Console.WriteLine("Items sold: " + level1[i].itemsSold);
            }
            Console.WriteLine("" + (level1.Length - 1) + " sellers have reached level 1");
        }
            //Same thing for level 2, 3 and 4
    }

What I'm trying to do is 4 new arrays for the different levels. I then loop through the array with all the salesmen and place the salesmen into the arrays in accordance to the number of sold items. I then check if the level-arrays are empty. If they aren't, I loop through them printing out the name, district and items sold for each salesman. Finally also printing out how many sellers there are in each level. When running the program, I get an error on the line

Console.WriteLine("Name: " + level1[i].name);

Saying "System.NullReferenceException has been thrown "Object reference not set to an instance if an object".

I would assume that means level1[i].name isn't referencing to an object but I don't really know how to go from there... Any advice or pointers would be greatly appriciated!


Solution

  • You are getting a System.NullReferenceException because you are initializing the level arrays with the same length as the salesmen array, but you are only adding salesmen to the level arrays based on their level.

    So there will be not initialized null elements in the level arrays, and when you try to access the name property of a null element, you get the exception because you try to read property of absent element.

    To fix this, you may use List<Salesman> instead of Salesman[]. List<T> is a generic dynamic array and you can iterate over its items in the same way:

    public static void sortering(Salesman[] salesmenArray)
    {
        var level1 = new List<Salesman>();
        var level2 = new List<Salesman>();
        var level3 = new List<Salesman>();
        var level4 = new List<Salesman>();
    
        for (int i = 0; i < salesmenArray.Length; i++)
        {
            if (salesmenArray[i].level == 1)
            {
                level1.Add(salesmenArray[i]);
            }
            else if (salesmenArray[i].level == 2)
            {
                level2.Add(salesmenArray[i]);
            }
            else if (salesmenArray[i].level == 3)
            {
                level3.Add(salesmenArray[i]);
            }
            else if (salesmenArray[i].level == 4)
            {
                level4.Add(salesmenArray[i]);
            }
        }
    
        if (level1Count > 0)
        {
            for (int i = 0; i < level1.Count; i++)
            {
                Console.WriteLine("Name: " + level1[i].name);
                Console.WriteLine("District: " + level1[i].district);
                Console.WriteLine("Items sold: " + level1[i].itemsSold);
            }
            Console.WriteLine("" + level1Count + " sellers have reached level 1");
        }
        //Same thing for level 2, 3 and 4
    }
    
    

    Here is some other improvments then you can do with your code. For example if Salesman.level may contains only values form the list [1, 2, 3, 4] you can store levels in the List of List<Salesman> or in the array of List<Salesman> and add items in the easier way. Also string interpolation is an easier, faster, and more readable string concatenation syntax.

    // here we creates a new array of lists and initialize it with 4 empty lists of Salesman
    var levels = new List<Salesman>[] 
    {
        new List<Salesman>(),
        new List<Salesman>(),
        new List<Salesman>(),
        new List<Salesman>()
    };
    
    foreach(var salesmen in salesmenArray)
    {
        // (salesmen.level - 1)-th list stores salesmen with that level
        levels[salesmen.level - 1].Add(salesmen);
    }
    
    // you can iterate salesmen of all levels with nested loops 
    for(int level = 0; level < levels.Lenth; level++)
    {
        foreach(var salesman in levels[level])
        {
            Console.WriteLine($"Name: {salesman.name}");
            Console.WriteLine($"District: {salesman.district}");
            Console.WriteLine($"Items sold: {salesman.itemsSold}");
        }
    
        // Count property gets the number of elements contained in the List<T> so you don't need to decrement this value for display the number of salesmen with this level 
        Console.WriteLine($"{levels[level].Count} sellers have reached level {level + 1}");
    }
    

    Finally there is an interesting mechanism to manipulate collections in .NET called LINQ. You can use LINQ syntax to select, filter, group and aggregate data. LINQ is readable efficiency and powerful tool. Here's a sample of your code rewritten with LINQ:

    foreach(var group in salesmenArray
        .GroupBy(salesman => salesman.level)
        .OrderBy(groups => groups.Key))
    {
        foreach(var salesman in group)
        {
            Console.WriteLine($"Name: {salesman.name}");
            Console.WriteLine($"District: {salesman.district}");
            Console.WriteLine($"Items sold: {salesman.itemsSold}");
        }
        Console.WriteLine($"{group.Count()} sellers have reached level {group.Key}");
    }