C# modifying a list in a function [duplicate] - c#

This question already has answers here:
Directly modifying List<T> elements
(6 answers)
Closed 8 years ago.
I am writing a function which is passed in a list which is partially filled. I'd like to set some of the fields within the list inside this function. I thought that passing it as a reference would allow me to do this, however, I get the following error:
Error 1 Cannot modify the return value of 'System.Collections.Generic.List.this[int]' because it is not a variable
I am wondering what I might need to do to tell C# that I wish to have the option of modifying the contents of the list.
Here is a summarized version of my code:
public static void Determine_RTMM_Descriptor(ref List<Struct_Descriptor_Type> symbols, string Dwarf_Output_Filename)
{
...
lines = System.IO.File.ReadAllLines(Dwarf_Output_Filename);
//loop on symbol names
for (int idx = 0; idx < symbols.Count; idx++)
{
if(symbols[idx].size == 0)
symbols[idx].size = (int)new System.ComponentModel.Int32Converter().ConvertFromString(split_line[DwarfInterface.SIZE_INDEX]);
...
}
Thanks in advance for any help.

The underlying issue here is that you have a list of value types. When you use the indexer of the list to get an item from the list you are getting a copy of that type. The code symbols[idx] is the value of that item. It is not a variable representing that item, as the error message is telling you.
You're trying to mutate the size of the copy, which will have no effect on the item of the list. This is such a common mistake that the compiler even makes this an error.
If you really are sure that you want to have a mutable value type (hint: you aren't, and you shouldn't have one; you almost certainly just want to have a class here to avoid this problem entirely) then you would need to get the value of the item, mutate it, and then set the item again:
if(symbols[idx].size == 0)
{
var symbol = symbols[idx];
symbol.size = 42;
symbols[idx] = symbol;
}

Your return type on the function is "void" when you should set the return type to the list. That should allow you to change it and return it modified.

Related

Index out of range using lists and for loop

I have this loop here, which for each question it is supposed to create, it generates and then formats a 'worded question' from an array of questions, such as; 'What is the sum of {0} + {1}?'. This loop then formats it, adds the worded question and the answer to an array.
// Use for loop to create the correct amount of questions
for (int i = 0; i < Data.Questions.numQuestions; i++)
{
Random rnd = new Random();
Data.Questions.Sum sum = new Data.Questions.Sum();
// Create part one and part two of the question using random numbers
// ex. 3 + 5
// 3 = partOne, 5 = partTwo
int partOne = rnd.Next(Data.Questions.Sum.min, Data.Questions.Sum.max);
int partTwo = rnd.Next(Data.Questions.Sum.min, Data.Questions.Sum.max);
// Randomly select one of the word questions
string fullQuestion = Data.Questions.Sum.wordedQuestions[rnd.Next(0, Data.Questions.Sum.wordedQuestions.Length)];
// Format the string with the generated numbers
fullQuestion = string.Format(fullQuestion, partOne, partTwo);
// Set out-of-class variables to be displayed to the user
Data.Questions.Sum.questions[i] = fullQuestion;
Data.Questions.Sum.answers[i] = partOne + partTwo;
}
Both Data.Questions.Sum.questions and Data.Questions.Sum.answers are List<string>'s and List<int>'s.
However, when this loop is run, with i = 0, I am thrown;
System.ArgumentOutOfRangeException: 'Index was out of range. Must be
non-negative and less than the size of the collection. Parameter name:
index'
Does anyone know what I'm doing wrong? As far as I know lists are dynamic, and I've defined like this;
// Arrays containing all questions and answers
// used to display questions and check answers
public static List<string> questions = new List<string>();
public static List<int> answers = new List<int>();
Also, to clarify, I do not want to use .Add(), as I have a settings panel which when you hit apply, re-runs this loop so the questions are up to date to the current settings. I need the loop to override the previous values.
Edit:
When using arrays, the better option here, I get;
System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'
On assigning Data.Questions.Sum.answers[i], after assigning the array like so; public static int[] answers {};
If you can't .Add() in this lists - create a copy of this lists and .Add() there. Lists has special for that kind of thing: new List<T>(IEnumerable<T>)
If you need to dynamically scale the collection, but you also need to iterate over it multiple times, then you'll need to check whether it is large enough to either insert, or just update.
You can do this with an extension method such as this...
public static class ListExtentions
{
public static void AddOrUpdate<T>(this List<T> that, int index, T value)
{
if (that.Count > index)
{
that[index] = value;
}
else
{
that.Add(value);
}
}
}
...which can then be called like this...
list.AddOrUpdate(index, value);
...however you can make things easier for yourself if you know how many questions you are going to have to start with.
If the number of questions changes when your UI changes, then you will also have to deal with the issue have scaling down the collection to ensure old elements are removed, which is much simpler if you just re-instantiate the collections every time you need to regenerate the questions / answers.
This is likly to be cause of your problem,(I have asked for clarification in comments where you didn't reply).
Still putting this as an answer, as it is potential error spot and you need to fix this.
As you mentioned you are facing this exception on i=0. there are high chanced that this is every time case not any specific case.
If Data.Questions.Sum.questions is empty then, Data.Questions.Sum.questions[i] = fullQuestion; , will surely throw such exception. Same way for Data.Questions.Sum.answers too.
In such case, you must use .Add() to insert into list.
so your code should be,
if (Data.Questions.Sum.questions.Count > i)
Data.Questions.Sum.questions[i] = fullQuestion;
else
Data.Questions.Sum.questions.Add(fullQuestion);
But if they are not empty, it must not be the cause of this exception.
One more thing i have noticed in your code is Data.Questions.Sum.wordedQuestions.
Even if you have valid list (here Data.Questions.Sum.wordedQuestions) - as you have Length prop, it must be Array not list.
If it is empty, while doing this
string fullQuestion = Data.Questions.Sum.wordedQuestions[rnd.Next(0, Data.Questions.Sum.wordedQuestions.Length)];
this line will surely throw
An unhandled exception of type 'System.ArgumentOutOfRangeException' occurred in mscorlib.dll
as you are trying to get 0th index's data from it.
So before fetching data from list or array, first you need to check if it does have data init, and also it does have that index which you are asking.
something like
string fullQuestion = string.Empty;
if (Data.Questions.Sum.wordedQuestions != null &&
Data.Questions.Sum.wordedQuestions.Length > 0)
{
//here the way you are creating random number,
// you are assured about index is present in array.
int indexForWordedQuestion = rnd.Next(0, Data.Questions.Sum.wordedQuestions.Length);
fullQuestion = Data.Questions.Sum.wordedQuestions[indexForWordedQuestion];
}

Array Questions

Recently had a quiz in my C# class and got some things wrong. I think I have the answers but I want to make sure I am right.
First one:
Explain the result
int[] myArray = {5,10,15,20,25};
DoWork(myArray);
void DoWork(int[] theArray)
{
for (int c = 0; c < theArray.Length; c++)
{
theArray[c] = 1;
}
theArray = null;
}
For this one, I only got half of it right. I said that the loop would set the value for each element in the array to 1. So my question is, what happens when you set the array to null?
Second one:
Explain the result
int[] myArray = {5,10,15,20,25};
DoWork(myArray[1]);
void DoWork(int theItem)
{
theItem = -1;
}
This one I got completely wrong. The correction was that myArray[1] = 10 still. Is this because it is not being passed by reference? This just confused me a lot because I ran a little test program on the first one (without the null part) and all the values were set to 1 in the array but I was not passing by reference.
Q: what happens when you set the array to null?
A: "theArray" (inside the routine) is set to null. But "myArray" (outside of the routine) is UNCHANGED. The reason is that "myArray" is an object reference, which is passed by value into DoWork().
Q: Is this because it is not being passed by reference?
A: Yes, exactly. From the link above:
https://msdn.microsoft.com/en-us/library/9t0za5es.aspx
Any changes to the parameter that take place inside the method have no
affect on the original data stored in the argument variable.
These links explain further:
C# Parameter Passing, Ref and Out
C# - Passing Parameters by Reference

Associate variable to a list [duplicate]

This question already has answers here:
Interesting "params of ref" feature, any workarounds?
(5 answers)
Closed 7 years ago.
I'm not sure if this is easy possible in C#. But I would like to get to know how this could be done easily.
public partial class Form1
{
// I left out the unimportant code for this example
private myControl cLeft,cTop,cBottom,cRight;
private List<myControl>mControls;
public Form1()
{
InitializeComponents();
//this list should contain the fields cLeft,cTop,cBottom,cRight...
mControls=new List<myControl>(){cLeft,cTop,cBottom,cRight};
/* now I want that cLeft and so on get assigned...
of course, this doesn't work because the list refers to the values of
cLeft ... which are null. So I would need to store a reference to those fields to get this work.*/
mControls.ForEach(x=>x=new myControl(this));
}
}
I'm sure it could be done through reflection, but I assume that there should be a way to do this easily in C# or isn't it possible?
It's just a simple loop, there is no need to use LINQ. You just need a for loop.
for (int i = 0 ; i < mControls.Count ; i++) {
mControl[i] = new myControl(this);
}
But, there is no need to write cLeft, cTop etc. You can just refer to them using the indexer: mControls[0], mControl[1] etc.
And remember, the foreach loop or the ForEach extension method doesn't work. This is because you are changing the reference of the variable. That is just another confusing (for beginners) feature of reference types!
Consider this method
public void ChangeReference (string s) {
s = "Hello";
}
And you call this method:
String s = "xxx";
ChangeReference (s);
Will s be "Hello" after the call? No. In the method, you are changing the location of the string in memory, but the argument is still in the same place!

array comparison(T) delegate NullReferenceException

I'm having a problem with the delegate not delegating...
I have an object called Tweet that has a string text and an int score. I want to sort the array of tweet objects(twtArray) in order of the score.
This is the code I have:
Array.Sort(twtArray, delegate(Tweet twt1, Tweet twt2)
{
return twt1.score.CompareTo(twt2.score); //(twt1.score - twt2.score)
});
and it throws:
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Array.FunctorComparer`1.Compare(T x, T y)
Whilst debugging, I noticed that the first comparison works but in the second comparison, twt2 is null. And it can't be null because I definitely have 8 elements in the array.
I've tried reversing twt1 and twt2 as well but makes no difference.
I also tried making my own comparison method in the Tweet class but again, same thing.
Any help would be appreciated!
Also, I dont think this is a duplicate of this question: List.Sort in C#: comparer being called with null object
because i tried all the possible solutions from this but it's not working. i've also searched a lot on google :(
Even if you have a Tweet[] with 8 elements some can be null:
Tweet[] twtArray = new Tweet[8]; // all instances are null
You: The Tweet[] is of size 20 and I can see that there are 8 Tweet objects in there (with correct text and score values) in the first line of my code.
So the array's size is 20 but only 8 are initialized? (see above)
Array.Sort needs to compare all with all others.
You could prevent it in this way:
Array.Sort(twtArray, delegate(Tweet twt1, Tweet twt2)
{
if(twt1 == null && twt2 == null) return 0;
if(twt1 == null) return -1;
if(twt2 == null) return 1;
return twt1.score.CompareTo(twt2.score);
});
When you're working with an array that's only partially filled, that indicates that you don't actually want an array, you want List<T>.
Using that, you can have a collection that contains 8 items, but can be later expanded to 20 efficiently. And when you call Sort() on such list, you're not going to have any problems with nulls.

C# SortedDictionary in-place update [duplicate]

This question already has answers here:
Error: "Cannot modify the return value" c#
(8 answers)
Closed 8 years ago.
I have a SortedDictionary defined as:
public SortedDictionary<DateTime,RosterLine> RosterLines = new SortedDictionary<DateTime,RosterLine>();
RosterLine itself is a simple struct:
struct RosterLine {
public string RosCd;
public string ActCd;
public double Hrs;
}
I can .Add(dt, rosterLine) no problems, and iterate through the dictionary fine too.
My problem is trying to update the RosterLine values given a specified date eg.
DateTime currDt = new DateTime(2013,12,02);
RosterLines[currDt].ActCd = "SO"; // error here
It tells me: Cannot modify the return value (dictionary def here) because it is not a variable. My goal is to do this with an iterating loop (which I thought might be the problem), but it won't work outside the loop on its own either (as above).
My question is: how do I update a SortedDictionary with a given key (date)?
The reason for the error message is that RosterLine is a struct and by that a value type. The error I get in ideone is:
Cannot modify a value type return value of
`System.Collections.Generic.SortedDictionary.this[System.DateTime]'.
Consider storing the value in a temporary variable
For value types, the dictionary stores a copy of the value and not a reference to the object on the heap. Also, when retrieving the value (as in dict[DateTime.Today]), it is copied again. Therefore, changing a property in the way you do in your sample only works on the copy of the value type. The compiler prevents misunderstandings by the error message - if it wouldn't one would wonder why the value in the dict has not been changed.
var dict = new SortedDictionary<DateTime, RosterLine>();
dict.Add(DateTime.Today, new RosterLine());
// Does not work as RosterLine is a value type
dict[DateTime.Today].ActCd = "SO";
// Works, but means a lot of copying
var temp = dict[DateTime.Today];
temp.ActCd = "SO";
dict[DateTime.Today] = temp;
In order to solve this, you could make RosterLine a class or you can work with temp variables as the error message suggests.

Categories