Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I'm new to C# and having trouble understanding the scope of variables. I've declared an array at the beginning of the class:
namespace TextProcessing
{
public static class Program
{
private static string[] ForewordArray;
Then in Main, I initialize and fill the array:
public static void Main(string[] args)
{
try
{
try
{
//snip - XML source is opened - snip
foreword = XMLnav.SelectSingleNode("//iso:foreword", NSmgr);
forewordText = foreword.InnerXml;
ForewordTextP = (forewordText.Length - forewordText.Replace("<p", string.Empty).Length) / 2 + 1;
CurrFPos = 1;
LastFPos = 1;
string[] ForewordArray = new string[ForewordTextP];
for (var z = 0; z <= ForewordTextP - 1; z++)
{
LastFPos = CurrFPos;
if (z < ForewordTextP - 1)
{
CurrFPos = Strings.InStr(LastFPos, forewordText, "<p");
if (LastFPos > 1)
{
ForewordArray[z] = Strings.Left(Strings.Mid(forewordText, LastFPos - 1), CurrFPos - LastFPos - 1);
}
else
{
ForewordArray[z] = Strings.Left(Strings.Mid(forewordText, LastFPos), CurrFPos - LastFPos - 1);
}
CurrFPos = CurrFPos + 1;
}
else
{
CurrFPos = Strings.InStr(LastFPos, forewordText, "p>");
ForewordArray[z] = Strings.Left(Strings.Mid(forewordText, LastFPos-1), CurrFPos - LastFPos + 3);
}
}
}
After some other text processing, when execution returns to the array (still in Main!), it's null:
foreach (var bkmkStart in wordDoc.MainDocumentPart.RootElement.Descendants<BookmarkStart>())
{
if (bkmkStart.Name == "ForewordText")
{
forewordbkmkParent = bkmkStart.Parent;
for (var y = 0; y <= ForewordArray.Length - 1; y++)
{
var TextProcessP = new Para2XML(ForewordArray[y]);
forewordbkmkParent.InsertBeforeSelf(TextProcessP.PReturn);
}
}
}
Is this happening because I initialize the array inside a Try loop?
I've read dozens of pages about variable scope in C# and I'm still not getting it. Thanks for any reading suggestions.
You are declaring a new local array inside Main and this is getting initialized with data. It looks like the second block of code is then accessing the class level array, which was never initialized.
In Main, the following line is declaring a new string array called ForewordArray because you start this line with the type.
string[] ForewordArray = new string[ForewordTextP];
In order to initialize the array you declared up at the top of the class, this should be as follows:
ForewordArray = new string[ForewordTextP];
Scope is actually pretty simple. If a class contains a variable, anything within that class sees it. However, anything outside that class/block cannot access that variable.
It's like a hierarchy of dropdowns. Click on a dropdown, and you see everything it contains. If it contains other dropdowns, click on those, and you see what they contain. Each dropdown only knows about the stuff it contains. This means that the dropdown which contains everything (or, in other words, within which everything is nested) can see everything.
There is a complication when variables are named the same way. If one class contains a variable, and a class within it contains another variable with the same name, the class inside will override the variable's name, because it is more precise in context.
Writing string[] ForewordArray = new ... declares a new array variable. So the containing class Program's ForewordArray is getting overridden by the child class' ForewordArray. The notation string[] creates a new variable of type string array. To initialize the variable instead, just use the variable name ForewordArray.
Related
This question already has answers here:
In C#, why can't I modify the member of a value type instance in a foreach loop?
(8 answers)
Closed 3 years ago.
Please consider the following code:
public class Program {
public struct A
{
public int Prop {get;set;}
}
public static void Main()
{
var obj = new A();
obj.Prop = 10;
var list = new List<A>(){obj};
foreach(var l in list) {
l.Prop = 20; //here I'm getting compile time error "Cannot modify members of 'l' because it is a 'foreach iteration variable'"
}
}
}
So my question is: why struct properties cannot be assigned while iterating over a list of structs?
Please note that even when iterating with simple for like this:
for (int i=0; i<list.Count(); ++i)
list[i].Prop = 20;
I'm still getting compile time error...
You cannot modify collection over which you are iterating with foreach.
Instead you should use for loop, which allows that:
for(int i = 0; i < list.Length; i++)
{
list[i].Prop = 200;
}
You can refer to this question: Why can't we assign a foreach iteration variable, whereas we can completely modify it with an accessor?
A hw was given to us to change a previous hw in C# which used 2d arrays and instead of using 2d arrays we use an Array list with variables declared in an object called Students.
I would like to use a method to calculate a student best mark; however, the method is giving me an error and a warning which are the following:
Error:
CS0161 'Form1.Calc_HighestMarkOutput(int)': not all code paths return a value.
Warning:
CS0162 Unreachable code detected.
Inside the arraylist the user inputed (through use of an overload constructor):
Student Name, Maths Mark, English Mark, Maltese Mark, Email Address.
and since in the method I am returning 3 highest marks in 3 subjects attained by all students, I decided to return an array. which will be accessed by a temporary array inside the main program by selectedindex.
Please help me find the problem.
And thanks in advance.
public int[] Calc_HighestMarkOutput(int HighestMarkIndex)
{
int[] HighestMarkOutput = new int[3];
int HighestMarkMaths = 0;
int HighestMarkEnglish = 0;
int HighestMarkMaltese = 0;
int TMPHighestMarkMaths = 0;
int TMPHighestMarkEnglish = 0;
int TMPHighestMarkMaltese = 0;
for (int i = 0; i < myStudents.Count; i++) //a loop through an array list.
{
if (myStudents[HighestMarkIndex].Maths_Result > HighestMarkMaths)
{
TMPHighestMarkMaths = myStudents[HighestMarkIndex].Maths_Result;
HighestMarkMaths = TMPHighestMarkMaths;
}
if (myStudents[HighestMarkIndex].English_Result > HighestMarkEnglish)
{
TMPHighestMarkEnglish = myStudents[HighestMarkIndex].English_Result;
HighestMarkEnglish = TMPHighestMarkEnglish;
}
if (myStudents[HighestMarkIndex].Maltese_Result > HighestMarkMaltese)
{
TMPHighestMarkMaltese = myStudents[HighestMarkIndex].Maltese_Result;
HighestMarkMaltese = TMPHighestMarkMaltese;
}
HighestMarkOutput[0] = HighestMarkMaths;
HighestMarkOutput[1] = HighestMarkEnglish;
HighestMarkOutput[2] = HighestMarkMaltese;
return HighestMarkOutput;
}
You are getting an error, because the return-statement is inside the loop. If the list is empty, the return statement will never be executed. Also, you know the result only after the loop has finished. So, place the return-statement after the loop.
Since the purpose of this method is to find the highest marks, it makes no sense to pass such an index into the routine as a parameter.
Using foreach is easier than for because you don't have to deal with indexes.
Instead of returning an array, return an unnamed student containing the results. You can drop useless temporary variables.
public Student Calc_HighestMarkOutput()
{
var result = new Student(); // You also might have to add a default constructor.
foreach (Student student in myStudents) {
if (student.Maths_Result > result.Maths_Result) {
result.Maths_Result = student.Maths_Result;
}
if (student.English_Result > result.English_Result) {
result.English_Result = student.English_Result;
}
if (student.Maltese_Result > result.Maltese_Result) {
result.Maltese_Result = student.Maltese_Result;
}
}
return result;
}
You could also use Math.Max to simplify finding the maximum value
foreach (Student student in myStudents) {
result.Maths_Result = Math.Max(result.Maths_Result, student.Maths_Result);
result.English_Result = Math.Max(result.English_Result, student.English_Result);
result.Maltese_Result = Math.Max(result.Maltese_Result, student.Maltese_Result);
}
With these refactorings, the method shrinks from 22 lines (not counting empty lines and lines containing only a brace) to 7 lines.
I searched the heck out of it, and i can't solve it.
I have a program setup like this (it's in Unity and Visual Studio 2019 for C#):
Note that the CSV loading goes fine, when i debug the code i can see everything filled with corect data.
#region character class
public class _Character
{
public int Id { get; set; }
public int Variation { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
}
#endregion
//Tools.LoadCsv generates a string[,] from a csv file
//Tools.IntParse parses int's with a lot of format error checking
void Start()
{
#region load characters class
string[,] CharacterCSV = Tools.LoadCsv(#"Assets/GameDB/character.csv");
List<_Character> Character = new List<_Character>();
for (int i = 1; i < CharacterCSV.GetUpperBound(0); i++)
{
_Character temp = new _Character();
temp.Id = Tools.IntParse(CharacterCSV[i, 0]);
temp.Variation = Tools.IntParse(CharacterCSV[i, 1]);
temp.Name = CharacterCSV[i, 2];
temp.LastName = CharacterCSV[i, 3];
Character.Add(temp);
}
CharacterCSV = null;
#endregion
}
I barely understand objects, so i'm sorry if i am doing it wrong.
The questions i have are:
Why does the Object List generation Háve to be in Start ? I can't seem to do that in it's own class.
How can i get an object from the Character Object List, containing Id = 100 and Name = "John"
, and access it from another class or method.
I ussualy frankenstein the heck out of code and make it good enough for me, but now i wanted to make something nice and cant seem to get to the objects.
Thanks in advance!
//the major issue was declaring the object inside the class, when declared outside the class, the List Object was available to the outside world.
List<_Character> Character = new List<_Character>(); move to outside Start{}
I'm not editing the question to correct the code, because the question needs to stay clear.
//
Why does the Object List generation has to be in Start ? I can't seem to do that in it's own class.
How can i get an object from the Character Object List, containing Id = 100 and Name = "John" , and access it from another class or method.
If you want to retrieve a character from outside of the class, then, you have to declare the list outside of the Start function, otherwise, the list will be destroyed since it's a local variable of the function.
// Declare the list outside of the functions
private List<_Character> characters;
void Start()
{
// Avoid using regions, they encourage you to make very long functions with multiple responsabilities, which is not advised
// Instead, create short and simple functions, and call them
LoadCharactersFromCSV();
}
void LoadCharactersFromCSV()
{
string[,] CharacterCSV = Tools.LoadCsv(#"Assets/GameDB/character.csv");
// If you can, indicate the approximate numbers of elements
// It's not mandatory, but it improves a little bit the performances
characters = new List<_Character>( CharacterCSV.GetUpperBound(0) );
// I believe `i` should start at 0 instead of 1
for (int i = 1; i < CharacterCSV.GetUpperBound(0); i++)
{
// I advise you to create a constructor
// instead of accessing the properties one by one
_Character temp = new _Character();
temp.Id = Tools.IntParse(CharacterCSV[i, 0]);
temp.Variation = Tools.IntParse(CharacterCSV[i, 1]);
temp.Name = CharacterCSV[i, 2];
temp.LastName = CharacterCSV[i, 3];
characters.Add(temp);
}
CharacterCSV = null;
}
// Using this function, you will be able to get a character from its id
public _Character GetCharacter( int id )
{
for (int 0 = 1; i < characters.Count; i++)
{
if( characters[i].Id == id )
return characters[i];
}
// Return null if no character with the given ID has been found
return null ;
}
Then, to call GetCharacter from another class:
public class ExampleMonoBehaviour : MonoBehaviour
{
// Replace `TheClassName` by the name of the class above, containing the `Start` function
// Drag & drop in the inspector the gameObject holding the previous class
public TheClassName CharactersManager;
// I use `Start` for the sake of the example
private void Start()
{
// According to your code, `_Character` is defined **inside** the other class
// so you have to use this syntax
// You can get rid of `TheClassName.` if you declare `_Character` outside of it
TheClassName._Character john = CharactersManager.GetCharacter( 100 );
}
}
I created a small piece of code in order to test part of my project. It is working however I have some questions...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace testtodel
{
class Program
{
private class BucketItems
{
private List<Item> ItemList;
private Item item;
public BucketItems()
{
//item = new Item();
ItemList = new List<Item>();
}
public void AddBucketItem(int _Start, int _End)
{
item = new Item();
item.SetItem(_Start, _End);
ItemList.Add(item);
}
public void PrintItemList()
{
for (int i = 0; i < ItemList.Count(); i++)
{
Console.WriteLine("Item: " + i.ToString() + " S: " + ItemList[i].Start.ToString() + " E: " + ItemList[i].End.ToString() + " D: " + ItemList[i].Range.ToString());
}
}
}
private class Item
{
public int Range { get; set; }
public int Start { get; set; }
public int End { get; set; }
public void SetItem(int _Start, int _End)
{
Start = _Start;
End = _End;
Range = _End - _Start;
}
}
static void Main(string[] args)
{
BucketItems bucketItems = new BucketItems();
bucketItems.AddBucketItem(0, 100);
bucketItems.AddBucketItem(200, 300);
bucketItems.AddBucketItem(700, 1000);
bucketItems.PrintItemList();
Console.ReadLine();
}
}
}
The thing which I do not fully understand is related to line
item = new Item();
As you can see in code there are 2 lines like that, one commented and one uncommented.
Part_1:
When code is executed as it is now, it will create a new instance of 'Item' class each time when 'AddBucketItem' method is called, then 'Item.SetItem' will set 'Start', 'End', 'Range' within 'item' variable and this will be added to 'ItemList'.
Part_2:
When I will comment exisiting 'item = new Item();' line and uncomment other one then I will expect the following. When new instance of BucketItem class is created it will also create a new instance of 'Item' as this is defined in the 'BucketItem' constructor. Then, when 'AddBucketItem' method is called, an 'item' varaible will be set by 'item.SetItem' and then, added to ItemList.
This is ok for the first iteration. However if I will call 'AddBucketItem' method again, with new 'Start' and 'End' parameters, this will also change already added ItemList[0].
Questions:
Why each call in Part_2 is changing also all elements which were already added to ItemList? The only explanation is that all elements from list are storing a reference to 'item' variable and when this variable is changed by changing (Start, End, Duriation) it wil also change all ItemList. But I don't know if this is correct explanation and if it is I don't know why it is behaving like this. Hovever I am assuming that answer is different becasue the following example will fill the list as expected even when I am doing exactly the same thing as in 'Part2'.. I have variable, I am changing value of variable, I am adding this variable to list.
int new_val;
List<int> _lst = new List<int>();
for (int i = 0; i < 10; i++)
{
new_val = i;
_lst.Add(new_val);
}
for (int i = 0; i < _lst.Count(); i++)
{ Console.WriteLine("ListIdx: " + i.ToString() + " Value: " + _lst[i].ToString()); }
Can someone please put some more light on my issue?
In your example, new_val is int which is value type.
At the same time, in the first code snippet, Item is a class, which is reference type.
If you use approach described in Part_2, then you actually operate with one object. So, your ItemList contains N items, which are all the same object.
In Part_1, every time you call AddBucketItem, you create a new object and append it to your list.
In Part_2, everytime you call AddBucketItem, you modify the single original Item object.
You may want to read some information on value types and reference types on MSDN:
https://msdn.microsoft.com/en-us/library/4d43ts61(v=vs.90).aspx
You need to understand that in C#, objects are passed by reference and primitive types are passed by value.
Therefore, your understanding for the AddBucketItem example is correct. The Item object is only created once in the BucketItems constructor. When you call AddBucketItem(), you are modifying the original instance and adding its reference into the list. So in the end, you have a list with individual item all pointing to the same object instance.
For the 2nd example you provide, it behaves differently because for int type, which is a primitive data type, the value passed into the Add() function is a copy of the integer, not a reference.
This question already has answers here:
Captured variable in a loop in C#
(10 answers)
Closed 2 years ago.
I have this piece of code:
int i = 0;
foreach(var tile in lib.dic.Values)
{
var ii = i;
var t = tile;
Button b = new Button( () = > { MainStatic.tile = t; } );
Checkbox c = new Checkbox( () = > { lib.arr[ii].b = !lib.arr[ii].b; } );
i++;
}
While the above code works as it should, this piece below:
int i = 0;
foreach(var tile in lib.dic.Values)
{
Button b = new Button( () = > { MainStatic.tile = tile; } );
Checkbox c = new Checkbox( () = > { lib.arr[i].b = !lib.arr[i].b; } );
i++;
}
…will always execute the delegates with the last values of i and tile variables. Why does this happen, and why do I have to make a local copy of those vars, especially non-reference type int i?
Known "issue", please check Eric's blog Closures, captured variables.
Microsof decided to go for a breaking change, and fix it in C# 5.
This is expected: when you make a lambda, compiler creates a closure. It will capture the value of a temporary variable in there, but it would not capture the value of loop variables and other variables that change after creation of the lambda.
The core of the issue is that the delegate creation and execution times are different. The delegate object is created while the loop is running, but it is called well after the loop has completed. At the time the delegate is called, the loop variable has the value that it reached at the time the loop has completed, resulting in the effect that you see (the value does not change, and you see the last value from the loop).
Forgetting to create a temporary variable for use in closures is a mistake so common that popular code analyzers (e.g. ReSharper) warn you about it.
You cannot use loop variables like this because by the time the delegate is executed the loop variable will likely be in its final (end of loop) state as it uses the value of the variable at the time the delete is executed, not created.
You need to make a local copy of the variable to get this to work:
int i = 0;
foreach(var tile in lib.dic.Values)
{
var tileForClosure = tile;
var iForClosure = i;
Button b = new Button( () = > { MainStatic.tile = tileForClosure ; } );
Checkbox c = new Checkbox( () = > { lib.arr[iForClosure].b = !lib.arr[iForClosure].b; } );
i++;
}
By creating a local copy on each loop the value does not change and so your delegate will use the value that you expect.