Getting a string to be a variable name - c#

I found this question but it's being used with an XML file so I don't really understand what is going on.
What I want to do is get my list of objects to get populated in my for loop. Right now I have this:
for (int i = 0; i < dogs.Length; i++)
{
dogs[i] = new Dog();
}
dogs[0].PictureBox = picDog0;
dogs[1].PictureBox = picDog1;
dogs[2].PictureBox = picDog2;
dogs[3].PictureBox = picDog3;
I want to do something like this:
for (int i = 0; i < dogs.Length; i++)
{
dogs[i] = new Dog();
dogs[i].PictureBox = StringToVariable("picDog" + i);
}
PictureBox is a property field in case that makes a difference.
StringToVariable() is the thing I don't know about. I don't even know what it would be called to search for it.

It's impossible to say for sure without a good, minimal, complete code example. But I would expect that the following statement should work in your scenario:
dogs[i].PictureBox = (PictureBox)Controls.Find("picDog" + i, true)[0];
That will search the children of the current control (which I assume in this case is your Form subclass) for each control in turn. This is somewhat inefficient, as it has to search the controls collection for each item, but as long as you have a relatively small number of items, this is likely not a problem.
Depending on how your Form is set up, the following might also work:
string prefix = "picDog";
foreach (PictureBox pictureBox in Controls.OfType<PictureBox>())
{
if (pictureBox.Name.StartsWith(prefix))
{
int index;
if (int.TryParse(pictureBox.Name.Substring(prefix.Length), out index))
{
dogs[index] = pictureBox;
}
}
}
That version inspects each child control just once, attempting to parse an index appended to the initial text of "picDog", and if it's successful, using that index to assign to your array directly. This has the advantage of scaling well to larger lists of controls, but may be overkill in your case.
Note that in both of the above examples I've left out any error checking. In either example, you would probably want to add some kind of handling in case (for the first example) the desired control couldn't be found, or (for the second example) if you find a control for which you can't parse the index, or fail to fill in one of the elements of the dogs array.
If for some reason neither of the above examples seem to work for you, please edit your post so that it includes a better code example.

Sometimes a simple solution can work well. How about this?
var picDogs = new [] { picDog0, picDog1, picDog2, picDog3 };
for (int i = 0; i < dogs.Length; i++)
{
dogs[i] = new Dog();
dogs[i].PictureBox = picDogs[i];
}
You could even do this:
var dogs = new [] { picDog0, picDog1, picDog2, picDog3 }
.Select(picDog => new Dog() { PictureBox = picDog })
.ToArray();

Related

How to "return" multiple times with for loop?

Hopefully this post gives more clarity as to what I am trying to achieve.
Objective: I want to spawn 20 apples(that have an attached button) from a list at runtime. When the apples are clicked they will spawn a popup with information pertaining to the apple that was clicked.
What I'm doing currently: I am using a for loop to run through the list to spawn the apples. I currently have the following code:
public class AppleInventory : MonoBehaviour
{
[SerializeField] private ApplesScript applPrefab;
[SerializeField] private Transform applParent;
public ApplesScript CreateApples()
{
var appl = Instantiate(applPrefab, applParent);
for (int i = 0; i < apples.Count; i++)
{
appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
}
return appl;
}
}
The Problem: The problem is that when I use the for loop and click on the button,it returns the following error: ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. The popup information also does not update.
Code without for loop: The code works to spawn one apple when I remove the for loop and set the int i = to a specific number, like below. It will give the correct popup info for any number that "i" is set to. This lets me know that it is not the rest of the code that is the issue. This leads me to believe it is the "return" line along with the for loop that is the issue. It seems I may need to "return" for each iteration but I am unsure of how to go about doing this.
public ApplesScript CreateApples()
{
int i = 7;
var appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
return appl;
}
Thank you,
-
UPDATE
The fix was so simple. I just ended up creating a new method specifically for the for loop and it worked the way I wanted. My code now looks like this:
public void StarterOfApplesCreation()
{
for (int i = 0; i < apples.Count; i++)
{
CreateApples(i);
}
}
public void CreateApples(int i)
{
var appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
}
You have two options. The conventional option is to create all the items first and then return them all in some sort of list, e.g.
public static void Main()
{
foreach (var thing in GetThings(5))
{
Console.WriteLine(thing.Number);
}
Console.ReadLine();
}
public static Thing[] GetThings(int count)
{
var things = new Thing[count];
for (var i = 0; i < count; i++)
{
things[i] = new Thing { Number = i };
}
return things;
}
The more modern option is to use an iterator. It actually will return one item at a time. It has the limitation that you have to use the items there and then - you won't have random access like you would an array or the like - but it also has advantages, e.g.
public static void Main()
{
foreach (var thing in GetThings(5))
{
Console.WriteLine(thing.Number);
}
Console.ReadLine();
}
public static IEnumerable<Thing> GetThings(int count)
{
for (var i = 0; i < count; i++)
{
var thing = new Thing { Number = i };
yield return thing;
}
}
The result of an iterator will usually be used as the source for a foreach loop or a LINQ query. Note that you can always call ToArray or ToList on the result of an iterator if you do want random access in specific situations, but you still have the advantages of an iterator elsewhere. For instance, let's say that your method produces 1000 items and you want to find the first one that matches some condition. Using my first example, you would have to create all 1000 items every time, even if the first one was a match. Using an iterator, because the items are processed as they are created, you can abort the process as soon as you find a match, meaning that you won't unnecessarily create the remaining items.
Note that my examples use the following class:
public class Thing
{
public int Number { get; set; }
}
You can copy and paste the code into a Console app that doesn't use top-level statements. The bones of the code will still work with top-level statements, but you'll need to make a few other modifications.
Store each separate "appl" that gets instantiated in an Array, ie appls[i]=appl
Do this within the for loop.
If you think about it, by putting the line "return appl;" outside the for loop, you are only storing that last game object, not all of them. Thats why creating an array of gameobjects and assigning them within the loop may work for you.

Does expanding arraylist of objects make a new object?

Assume we have an array list of type Employe , does expanding it's length by 1 make a new object in the list ?
is the code in else statement correct? and is it recommended?
public void ModifierEmp(int c)
{
for(int i = 0; i < Ann.Count; i++)
{
if(Ann[i].Code == c)
{
Ann[i].saisie();
} else
{
i = Ann.Count + 1; //expanding arraylist ann
Ann[i].saisie(); //saisie a method for the user to input Employe infos
}
}
}
https://imgur.com/VfFHDKu "code snippet"
i = Ann.Count + 1;
The code above is not expanding the list: it is only setting your index variable (i) to have a new value.
If you wanted to make the list bigger, you would have to tell it which object to put into that new space you create. For example:
Ann.Add(anotherItem);
Of course, this gives you the ability to decide whether to add an existing item, create a new item (e.g. Ann.Add(new Something() { Code = c })), or even add a null value to the list (which is not usually a good idea).

c# collections and re-numbering not working as expected

Hi i'm trying to setup simple test data.
I simply want to take a collection which is smallish and make it bigger by add itself multiple times.
After I;ve added them together i want to re-number the property LineNumber
so that there are no duplicates and that it goes in order. 1,2,3,4....
no matter what i try it doesn't seem to work and i cant see the mistake.
var sampleTemplateLine = dataContext.TemplateFileLines.ToList();
*//tired this doesnt work either*
//List<TemplateFileLine> lineRange = new List<TemplateFileLine>();
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
var allProducts = sampleTemplateLine
.Concat(sampleTemplateLine)
.Concat(sampleTemplateLine)
.Concat(sampleTemplateLine)
.ToList();
int i = 1;
foreach (var item in allProducts)
{
item.LineNumber = i;
i++;
}
this doesnt seem to work either
//re-number the line number
var total = allProducts.Count();
for (int i =0; i < total; i++)
{
allProducts[i].LineNumber = i+1;
}
PROBLEM: below RETURN 4 when i'm expecting 1
var itemthing = allProducts.Where(x => x.LineNumber == 17312).ToList();
You are adding the same objects multiple times. You wold have to add new objects or clone the ones you have.
The problem is they are pointing the same object. So if you change a property it changes all the pointed objects at the same
You can use Clone method if it exist, if not you can create your own Clone method like in this question.

Dynamic Variable Name Use in C# for WinForms

Not sure what is the best way to word this, but I am wondering if a dynamic variable name access can be done in C# (3.5).
Here is the code I am currently looking to "smarten up" or make more elegant with a loop.
private void frmFilter_Load(object sender, EventArgs e)
{
chkCategory1.Text = categories[0];
chkCategory2.Text = categories[1];
chkCategory3.Text = categories[2];
chkCategory4.Text = categories[3];
chkCategory5.Text = categories[4];
chkCategory6.Text = categories[5];
chkCategory7.Text = categories[6];
chkCategory8.Text = categories[7];
chkCategory9.Text = categories[8];
chkCategory10.Text = categories[9];
chkCategory11.Text = categories[10];
chkCategory12.Text = categories[11];
}
Is there a way to do something like ("chkCategory" + i.ToString()).Text?
Yes, you can use
Control c = this.Controls.Find("chkCategory" + i.ToString(), true).Single();
(c as textBox).Text = ...;
Add some errorchecking and wrap it in a nice (extension) method.
Edit: It returns Control[] so either a [0] or a .Single() are needed at the end. Added.
for(...)
{
CheckBox c = this.Controls["chkCategory" + i.ToString()] as CheckBox ;
c.Text = categories[i];
}
You can do that with reflection. But don't.
It's more proper to instantiate a list of contols, add them programmatically to your form, and index that.
Sometimes it can help to put your controls into an array or collection as such:
Checkbox[] chkCataegories = new Checkbox[] { chkCategory1, chkCategory2 ... };
for(int i = 0; i < chkCategories.Length; i++)
chkCategories[i].Text = categories[i];
As another approach, you can dynamically create your checkboxes at runtime instead of design time:
for(int i = 0; i < categories.Length; i++)
{
Checkbox chkCategory = new chkCategory { Text = categories[i] };
someContainer.Controls.Add(chkCategory);
}
At least with dynamically created controls, you don't need to modify your GUI or your form code whenever you add new categories.
You don't need dynamic for that. Put chkCategory1 - 12 in an array, and loop through it with a for loop. I would suggest you keep it around in a field and initialize it at form construction time, because chkCategory seems to be related. But if you want a simple example of how to do it in that simple method, then it would be something like this:
private void frmFilter_Load(object sender, EventArgs e)
{
var chkCategories = new [] { chkCategory1, chkCategory2, chkCategory3, .......... };
for(int i = 0 ; i < chkCategories.Length ; i++ )
chkCategoies[i].Text = categories[i];
}
You know more about the application, so you could perhaps avoid writing out all the control names - for instance, if they are placed on a common parent control, then you could find them by going through it's children.
No, but you could do something like this (untested, beware of syntax errors):
private readonly CheckBox[] allMyCheckboxes = new CheckBox[] { chkCategory1, chkCategory2, ... }
Then you just need to do
for (i = 0; i < 12; i++) allMyCheckboxes[i].Text = categories[i];
The "this.Controls["chkCategory" + i.ToString()]" and "this.Controls.Find("chkCategory" + i.ToString(), true)" both do not work... the former informs you that the contents of the [] are not an int and the latter that ControlCollection does not contain a definition for Find.
Use "Control myControl1 = FindControl("TextBox2");" instead.
I needed this form as I was looping through another array, extracting values and using them to populate form fields. Much easier to look for label1, label2, label3, etc.

How to write this in better way?

Let's look at this code:
IList<IHouseAnnouncement> list = new List<IHouseAnnouncement>();
var table = adapter.GetData(); //get data from repository object -> DataTable
if (table.Rows.Count >= 1)
{
for (int i = 0; i < table.Rows.Count; i++)
{
var anno = new HouseAnnouncement();
anno.Area = float.Parse(table.Rows[i][table.areaColumn].ToString());
anno.City = table.Rows[i][table.cityColumn].ToString();
list.Add(anno);
}
}
return list;
Is it better way to write this in less code and better fashion (must be :-) )? Maybe using lambda (but let me know how)?
Thanks in advance!
Just FYI, you're never adding the new HouseAnnouncement to your list, and your loop will never execute for the last row, but I'm assuming those are errors in the example rather than in your actual code.
You could do something like this:
return adapter.GetData().Rows.Cast<DataRow>().Select(row =>
new HouseAnnouncement()
{
Area = Convert.ToSingle(row["powierzchnia"]),
City = (string)row["miasto"],
}).ToList();
I usually go for readability over brevity, but I feel like this is pretty readable.
Note that while you could still cache the DataTable and use table.powierzchniaColumn in the lambda, I eliminated that so that you didn't use a closure that wasn't really necessary (closures introduce substantial complexity to the internal implementation of the lambda, so I avoid them if possible).
If it's important to you to keep the column references as they are, then you can do it like this:
using (var table = adapter.GetData())
{
return table.Rows.Cast<DataRow>().Select(row =>
new HouseAnnouncement()
{
Area = Convert.ToSingle(row[table.powierzchniaColumn]),
City = (string)row[table.miastoColumn],
}).ToList();
}
This will add complexity to the actual IL that the compiler generates, but should do the trick for you.
You could do something like this in Linq:
var table = adapter.GetData();
var q = from row in table.Rows.Cast<DataRow>()
select new HouseAnnouncement()
{ Area = float.Parse(row[table.areaColumn].ToString()),
City = row[table.cityColumn].ToString()
};
return q.ToList();
Your "if statement" is not necessary. Your "for loop" already takes care of that case.
Also, your "for loop" will not execute when the number of your Table Rows is 1. This seems like a mistake, and not by design, but I could be wrong. If you want to fix this, just take out the "-1":
for (int i = 0; i < table.Rows.Count; i++)
Well, for one thing, you appear to have an off-by-one error:
for (int i = 0; i < table.Rows.Count - 1; i++)
{
}
If your table has three rows, this will run while i is less than 3 - 1, or 2, which means it'll run for rows 0 and 1 but not for row 2. This may not be what you intend.
Can't go much simpler that one for-loop and no if-statements:
var table = adapter.GetData(); //get data from repository object -> DataTable
IList<IHouseAnnouncement> list = new List<IHouseAnnouncement>(table.Rows.Count);
for (int i = 0; i < list.Length; i++)
{
list[i] = new HouseAnnouncement();
list[i].Area = float.Parse(table.Rows[i][table.areaColumn].ToString());
list[i].City = table.Rows[i][table.cityColumn].ToString();
}
return list;
It takes more characters than linq-version, but is parsed faster by programmer's brain. :)
Readability is, to me, preferable to being succinct with your code--as long as performance is not a victim. Also, I am sure that anyone who later has to maintain the code will appreciate it as well.
Even when I am maintaining my own code, I don't want to look at it, say a couple of months later, and think "what the hell was I trying to accomplish"
I might do something like this:
var table = adapter.GetData(); //get data from repository object -> DataTable
return table.Rows.Take(table.Rows.Count-1).Select(row => new HouseAnnouncement() {
Area = float.Parse(row[table.powierzchniaColumn].ToString()),
City = row[table.miastoColumn].ToString()
}).ToList();

Categories