0

I have the following code which is intended, as an example, to take a fruitName and assign it to the next available unused "name#" string variable (one that does not already have a fruit name assigned to it).

Wanting to avoid using nested IF statements, i am trying to use a Dictionary as follows.

public static void AssignFruitToNextAvailableSlot(string fruitName)
        {
            string NextEmptyNameSlot = "";

            string Name1 = "Apple";
            string Name2 = "Orange";
            string Name3 = "Tomato";
            string Name4 = "";
            string Name5 = "";
            string Name6 = "";
            string Name7 = "";
            string Name8 = "";
            string Name9 = "";


            Dictionary<string, string> nameSlots = new Dictionary<string, string>()
                {
                    {"Slot1", Name1},
                    {"Slot2", Name2},
                    {"Slot3", Name3},
                    {"Slot4", Name4},
                    {"Slot5", Name5},
                    {"Slot6", Name6},
                    {"Slot7", Name7},
                    {"Slot8", Name8},
                    {"Slot9", Name9}
                };

            foreach (KeyValuePair<string, string> nameSlot in nameSlots)
            {
                NextEmptyNameSlot = nameSlot.Key;

                if (nameSlot.Value == "")
                {
                    break;
                }
            }
            Console.WriteLine($"Available name slot at {NextEmptyNameSlot}");
            Console.WriteLine($"Content of empty name slot \"{nameSlots[NextEmptyNameSlot]}\"");
            nameSlots[NextEmptyNameSlot] = fruitName;
            Console.WriteLine($"Empty image slot has been assigned the value {nameSlots[NextEmptyNameSlot]}");
            Console.WriteLine($"Empty image slot has been assigned the value {Name4}");
            Console.ReadLine();
        }

Sample output for AssignFruitToNextAvailableSlot("Strawberry") :

  • Available name slot at Slot4
  • Content of empty name slot ""
  • Empty image slot has been assigned the value Strawberry
  • Empty image slot has been assigned the value

As you can see the code works fine to identify the empty name slot, in this case Slot4. However when using the syntax...

nameSlots[NextEmptyNameSlot] = fruitName

... "strawberry" is assigned to nameSlots[NextEmptyNameSlot], but not the variable Name4. I tried using "ref" to assign by reference but that yielded various error messages.

** What is the right syntax to assign the fruitName "Strawberry" to the Name4 string variable using the dictionary? Sorry if this is a very basic question. I am new to C#. **

  • 1
    `Dictionary` **explicitly** does not have guaranteed order (https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2?view=netframework-4.8#remarks). It is the wrong data structure for you to use. You likely want to use a `List`. The benefit of `List` is also you don't need to worry about 'empty slots'. You just `Add` entries as needed to the end of the list. – mjwills Apr 15 '20 at 05:08
  • Thanks mjwills. Noted regarding Dictionaries not having guaranteed order. Still, what would be the syntax to assign Name4 a string value via the dictionary nameSlots. – scorpiotomse Apr 15 '20 at 05:15
  • It isn't possible @scorpiotomse. You seem to think that changing the `Dictionary` will change `Name4` somehow. This is not true. This is not how things work. If you want to read out the value of `nameSlots["Slot4"]` then do that - but altering that value won't impact `Name4` is any way. – mjwills Apr 15 '20 at 05:17

3 Answers3

1

I think you are making this more complex than it needs to be. If you have a fixed number of elements and you want the order to remain the same then an array is the simplest way to handle the data.

Declare the array with 9 elements.

string[] fruitArray = new string[9];

Then keep a counter of how many slots you have used;

int NextSlot = 0;

Increment it after you add a fruit

NextSlot++;

Then you can iterate it to display the data

for(int loop =0; loop <= fruitArray.Length; loop++)
{
   Console.WriteLine($"Slot {loop + 1} contains the value {fruitArray[loop]}");
}

If you don't have a fixed number of elements, or you don't know the size during design time then you can use a List<string> instead of an array.

A List will keep the insertion order of the data, until you sort it.

jason.kaisersmith
  • 8,712
  • 3
  • 29
  • 51
1

While it's not the primary problem here, a confusing factor is that strings are immutable, which means if you "change it" then it's thrown away and a new one is created. I'll come back to this point in a moment

The major problem here is that you can not establish a reference to something:

string s = "Hello";

Then establish another reference to it (like you're doing with dictionary)

string t = s;

Then "change the original thing" by changing this new reference out for something else:

t = "Good bye";

And hope that the original s has changed. s still points to a string saying "Hello". t used to point to "Hello" also (it never pointed to s pointed to hello in some sort of chain) but now points to a new string "Good bye". We never used the new keyword when we said "Good bye" but the compiler had to use it, and new made another object and changed the reference to it. Because references aren't chained, you cannot change what a downstream variable points to and hope that an upstream variable pointing to the same thing will also change.

//we had this
s ===> "Hello" <=== t

//not this
t ==> s ==> "Hello"

//then we had this
s ===> "Hello"      t ===> "Good bye"

Because we have two independent reference that point to the same thing, the only way you can operate on one reference and have the other see it is by modifying the contents of what they point to, which means you will have to use something mutable, and not throw it away. This is where string confuses things because strings cannot be modified once made. They MUST be thrown away and a new one made. You don't see the new - the compiler does it for you.

So instead of using strings, we have to use something like a string but can have its contents altered:

StringBuilder sb1 = new StringBuilder("Hello");
StringBuilder sb2 = null;

var d = new Dictionary<string, StringBuilder>();

d["sb1"] = sb1;
d["sb2"] = sb2;

Now you can change your string in the mutable sb1, by accessing the stringbuilder via the dictionary:

d["sb1"].Clear();
d["sb1"].Append("Good bye");

Console.Write(sb1.Length); //prints 8, because it contains: Good bye

But you still cannot assign new references to your sb variables using the dictionary:

d["sb2"] = new StringBuilder("Oh");

//sb2 is still and will always be null
Console.Write(sb2.Length); //null reference exception

Using new here stops the dictionary pointing to sb2 and points it to something else, sb2 is not changed and is still null. There is no practical way to set sb2 to a stringbuilder instance, by using the dictionary d

This is also why the original string thing didn't work out - you can't change the content of a string - c# will throw the old string away and make a new one and every time new is used any reference that might have pointed to the old thing will now point to a new thing

As a result you'll have to init all your references to something filled or empty:

var sb1 = new StringBuilder("Hello");
var sb2 = new StringBuilder("Goodbye");
var sb3 = new StringBuilder("");
var sb4 = new StringBuilder("");

You'll have to link your dictionary to all of them:

d["sb1"] = sb1;
d["sb2"] = sb2;
d["sb3"] = sb3;
d["sb4"] = sb4;

And you'll have to skip through your dictionary looking for an empty one:

//find empty one
for(int i = 1, i <= 4; i++){
  if(d["sb"+i].Length ==0)
    return "sb"+i;
}

And then change its contents


This is all maaassively complex and I wholeheartedly agree with the answer given by jason that tells you to use arrays (because it's what they were invented for), but I hope i've answered your questions as to why C# didn't work the way you expected

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
  • Thank you all for taking the time to review this question. @Caius Jard: thank you for your detailed explanation. I now understand why the way i was trying to use Dictionaries is wrong. I will consider the different alternatives mentioned. – scorpiotomse Apr 15 '20 at 05:52
  • 1
    You're most welcome; I think we've all made this reasonable assumption when starting out! – Caius Jard Apr 15 '20 at 05:54
-1

If such strange thing is absolutely necessary, you can use Reflection.

var frutSlots = this.GetProperties()
.Where(p => p.Name.StartsWith("Name")).OrderBy(p => p.Name).ToList();

You'll get List of PropertyInfo objects, ordered by property name, through which you can iterate or just use Linq.

fruitSolts.First(fs => fs.GetValue(this).ToString()="").SetValue(this."somefruit");

But mind that reflections are not too quick and not too good for performance.