Bug in C# System.Collections.Generic.List<T>? - c#

I'm writing a simple code to read some data from a text file and storing in a C# List but having problems with it. Please help if the problem is at my side or is it the library. I've written the following function :
public List<EmpBO> ReadData()
{
EmpBO temp = new EmpBO();
List<EmpBO> lis = new List<EmpBO>(100);
string[] tokens;
string data;
StreamReader sw = new StreamReader(new FileStream("emp.txt",FileMode.OpenOrCreate));
int ind = 0;
while ((data = sw.ReadLine())!=null)
{
Console.WriteLine("Reading " + data);
tokens = data.Split(';');
temp.Id = int.Parse(tokens[0]);
temp.Name = tokens[1];
temp.Salary = double.Parse(tokens[2]);
temp.Br = double.Parse(tokens[3]);
temp.Tax = double.Parse(tokens[4]);
temp.Designation = tokens[5];
//lis.Add(temp);
lis.Insert(ind,temp);
ind++;
}
sw.Close();
Console.WriteLine("Read this material and returning list");
for (int i = 0; i < lis.Count; i++)
{
Console.WriteLine("" + (lis.ElementAt(i)).Name);
}
//foreach (EmpBO ob in lis)
//{
// Console.WriteLine("" + ob.Id + ob.Name);
//}
return lis;
}
File emp.txt Contains:
1;Ahmed;100000;20;1000;manager
2;Bilal;200000;15;2000;ceo
Now as you can see that in while loop, I've displayed what StreamReader has read and it does 2 iterations in this case and displays.
Reading 1;Ahmed;100000;20;1000;manager
Reading 2;Bilal;200000;15;2000;ceo
and as you can see i'm saving this info in temp and inserting in the list.
after the while loop is finished , when I traverse the list for knowing that what is stored in it then it displays:
Read this material and returning list
Bilal
BIlal
Well, the second record is stored in the list twice and 1st record is absent.. What seems to be the problem? I've used Add() method too , and foreach loop for traversing list as you can see it's commented out but the result was same.. Please help

Move this line
EmpBO temp = new EmpBO();
into the while-loop so that it looks like
while ((data = sw.ReadLine())!=null){
EmpBO temp = new EmpBO();
Console.WriteLine("Reading " + data);
tokens = data.Split(';');
temp.Id = int.Parse(tokens[0]);
temp.Name = tokens[1];
temp.Salary = double.Parse(tokens[2]);
temp.Br = double.Parse(tokens[3]);
temp.Tax = double.Parse(tokens[4]);
temp.Designation = tokens[5];
//lis.Add(temp);
lis.Insert(ind,temp);
ind++;
}
You are not creating a new EmpBO for each entry, but more overwriting the same object with the read values and adding it again to the List.
The effect is that you add the same object mutiple times to the List.

In your code you have created the EmpBO object only once. In the second iteration you are modified the value in the same object. you have to create instance for EmpBO inside the while loop like below.
while ((data = sw.ReadLine())!=null)
{
Console.WriteLine("Reading " + data);
tokens = data.Split(';');
EmpBO temp = new EmpBO();
temp.Id = int.Parse(tokens[0]);
temp.Name = tokens[1];
temp.Salary = double.Parse(tokens[2]);
temp.Br = double.Parse(tokens[3]);
temp.Tax = double.Parse(tokens[4]);
temp.Designation = tokens[5];
//lis.Add(temp);
lis.Insert(ind,temp);
ind++;
}

This isn't a direct answer to the question, but your code has other problems.
Both your FileStream and StreamReader should be disposed of after use.
Alternatively, you could write your code like this:
public List<EmpBO> ReadData()
{
return File
.ReadAllLines("emp.txt")
.Select(data =>
{
var tokens = data.Split(';');
return new EmpBO()
{
Id = int.Parse(tokens[0]),
Name = tokens[1],
Salary = double.Parse(tokens[2]),
Br = double.Parse(tokens[3]),
Tax = double.Parse(tokens[4]),
Designation = tokens[5],
};
})
.ToList();
}
That, hopefully, should be even easier.

You've inserted the same object twice. You have to create a new object in the loop otherwise you will override the attributes on each iteration and simply and a reference to the same object over and over again.It's safe to assume that standard operations on the BCL classes work correctly or as Eric Lippert put's it Maybe there's something wrong with the universe but probably not
you simply need to change the start of the loop to this:
while ((data = sw.ReadLine())!=null)
{
EmpBO temp = new EmpBO();

If you try to add same object twice in a list ,it will override values entered first time and will show only values from second object but twice
for example :Take a list ,add a object in it . modify that object ,again add it .
when you try to print values ,you will get values of last object
ob1.a=5;
list1.add(ob1);
// list1[0]-->a-->5
ob1.a=7;
list1.add(ob1);
// list1[0]--->a--->7 list1[1]--->a--->7

Related

C# how to write the whole array if a string within "array[1]" contains keyword

My current code is looping through a list containing saved strings in an array. Currently it looks for all strings in that array. I want to change this so that it only goes through (searching, looking) for strings within "log[1]"
Sorry, i dont know the word for "log[1]". Im new to programming. Keep reading and i think you will understand.
This is how i want to do it:
foreach (string[] item[1] in loggbok)
item[1] being log[1]. Number 1 is very important because I want to search only within log[1].
This is my current code for saving the whole array in my list:
List<string[]> loggbok = new List<string[]> { };
string[] log = new string[3]; //date, title, post
DateTime date = DateTime.Now;
log[0] = "\n\tDate: " + date.ToLongDateString() + " Kl: " + date.ToShortTimeString();
Console.WriteLine(log[0]);
Console.Write("\tTitle: ");
log[1] = "\tTitle: " + Console.ReadLine();
Console.Write("\tPost: ");
log[2] = "\tPost: " + Console.ReadLine();
loggbok.Add(log);
log = new string[3];
I save "log[1],log[2],log[3]"
The following code i want to make a search function which goes through my list and recognise all the strings within log[1] aka titles. If a string title is containing the users keyword all logs should join and the log will be printed.
As of now. I solved this by searching through all logs(1,2,3). This means that my program is searching currently for strings within (titles, date, posts). This makes it so that you can search for messages or "post" when i want the user to be restricted by only searching for titles.
So i thought maby if in my foreach loop i make "item" to "item[1]". Will that make my code to only look for "log[1]". I did not get that far though becouse writing "item[1]" is invalid syntax.
Current search function:
string key;
Console.Write("\n\tSearch: ");
key = Console.ReadLine();
//Searching through all log[] in loggbok.
//I want to change this line to item[1]
foreach (string[] item in loggbok)
{
//goes deeper and looks for all strings within log[].
foreach (string s in item)
{
//if a string is found containing key word, this block will run.
if (s.Contains(key))
{
foundItem = true;
Console.WriteLine(String.Join("\r\n", item));
index++;
}
}
}
Probably you can do it like this:
var result = loggbok.FirstOrDefault(x=> x.Any(s=> s.Contains(key));
Console.WriteLine(result?? "No record found");
You don't even need to loop, so what you need to do is retrieve the item from loggbok by the index.
// assign loggbokx of index 1, to variable item.
string[] item = loggbok[1];
// item will then have the 2nd (index=1) logbook.
// Note that index starts from 0.
// If you want to have the first one, then it should be loggbox[0]
// to make it even simpler you can write
// var item = loggbok[1];
// and the rest is the same...
//goes deeper and looks for all strings within log[].
foreach (string s in item)
{
//if a string is found containing key word, this block will run.
if (s.Contains(key))
{
foundItem = true;
Console.WriteLine(String.Join("\r\n", item));
index++;
}
}
Let's do it right!
Create a model class for your log:
class LogEntry
{
public DateTime Date { get; set; }
public string Title { get; set; }
public string Post { get; set; }
public override string ToString()
{
return "Date: " + Date.ToLongDateString() + " Kl: " + Date.ToShortTimeString()
+ "\tTitle: " + Title + "\tPost: " + Post;
}
}
Now we can comfortably use this model.
Let's populate the list with more records:
List<LogEntry> loggbok = new List<LogEntry>();
for (int i = 0; i < 5; i++)
{
LogEntry entry = new LogEntry();
entry.Date = DateTime.Now;
entry.Title = "title" + i;
entry.Post = "post" + i;
loggbok.Add(entry);
}
Let's print it:
foreach (var entry in loggbok)
Console.WriteLine(entry);
Due to the ToString method overload output looks out nice.
Let's find something:
string key = "title3";
var found = loggbok.Find(log => log.Title == key);
Console.WriteLine("Found:\n" + found);
We can use different methods of the List class, and LINQ extension methods.
If you need to save your data to a file and then read them from there, you can use json serialization.
For example, let's use the JavaScriptSerializer (don't forget to add a reference to the assembly):
JavaScriptSerializer jss = new JavaScriptSerializer();
// Save
File.WriteAllText("test.txt", jss.Serialize(loggbok));
// Load
loggbok = jss.Deserialize<List<LogEntry>>(File.ReadAllText("test.txt"));
This is the solution if anyone finds it intressting.
foreach (string[] item in loggbok)
{
foreach (string s in item)
{
//This was the magic line.
string searchTitle = item[1].ToLower();
if (searchTitle.Contains(titleKey.ToLower()))
{
Console.WriteLine("\n\tSearch hit #" + index);
foundItem = true;
Console.WriteLine(String.Join("\r\n", item));
index++;
break;
}
}
}

How to create dynamic buttons based on an arraylist

I'm having some trouble creating a foreach loop that creates buttons dynamically based on a List that is inside the NamesDA class.
I'm getting errors such as: Cannot convert type 'Program1.Names' to 'int'. I've tried what I know to fix the conversion error, but I don't know the correct way to do it.
Edit 1: allNames is an array list inside NamesDA that reads a csv file.
It returns a list of strings and int's, which then they are to be used to create the buttons and represent them.
Edit 2: The foreach loop problem is solved now, but I'm unable to get the values of column[0] for button text and column[1] for button tag.
The NamesDA class:
private const string path = "names.csv";
public static List<Names> GetNames()
{
StreamReader textIn = new StreamReader(new FileStream(path, FileMode.OpenOrCreate, FileAccess.Read));
List<Names> allNames = new List<Names>();
while (textIn.Peek() != -1)
{
string row = textIn.ReadLine();
string[] columns = row.Split(',');
allNames.Add(new Names(columns[0].ToString(), Convert.ToInt16(columns[1])));
}
textIn.Close();
return allNames;
}
The form:
int startTop = 20;
int startLeft = 17;
allNames = NamesDA.GetNames(); //calling the method in the NamesDA class
foreach (int x in allNames) {
names[x] = new Button();
tempButton.Text = ""; //based on the list column[0]
tempButton.Tag = ""; //based on the list column[1]
names[x].Location = new System.Drawing.Point(startTop + (x * 95), startLeft);
listView.Controls.Add(names[x]);
}
From the Updates it is clear that allNames is a List<Names>, where Names is a class contains two properties/fields one is of type int(let it be _id) and the another one is of type string(let it be _name). So you have to re create the loop as like the following:
Updates : You can Set the button location as well, if you need that you have to define two integer properties in the class(let it be int positionX=10 and int PositionY=30) Now take a look at the updated code:
int nextLeft=30;
foreach (Names name in allNames)
{
Button tempButton = new Button();
tempButton.Name = name._id;
tempButton.Text = name._name;
tempButton.Location = new System.Drawing.Point(name.positionX + nextLeft,name.positionY);
listView.Controls.Add(tempButton);
nextLeft+=30;
}

Trying to access variable from outside foreach loop

The application I am building allows a user to upload a .csv file, which will ultimately fill in fields of an existing SQL table where the Ids match. First, I am using LinqToCsv and a foreach loop to import the .csv into a temporary table. Then I have another foreach loop where I am trying to loop the rows from the temporary table into an existing table where the Ids match.
Controller Action to complete this process:
[HttpPost]
public ActionResult UploadValidationTable(HttpPostedFileBase csvFile)
{
var inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
var cc = new CsvContext();
var filePath = uploadFile(csvFile.InputStream);
var model = cc.Read<Credit>(filePath, inputFileDescription);
try
{
var entity = new TestEntities();
var tc = new TemporaryCsvUpload();
foreach (var item in model)
{
tc.Id = item.Id;
tc.CreditInvoiceAmount = item.CreditInvoiceAmount;
tc.CreditInvoiceDate = item.CreditInvoiceDate;
tc.CreditInvoiceNumber = item.CreditInvoiceNumber;
tc.CreditDeniedDate = item.CreditDeniedDate;
tc.CreditDeniedReasonId = item.CreditDeniedReasonId;
tc.CreditDeniedNotes = item.CreditDeniedNotes;
entity.TemporaryCsvUploads.Add(tc);
}
var idMatches = entity.Authorizations.ToList().Where(x => x.Id == tc.Id);
foreach (var number in idMatches)
{
number.CreditInvoiceDate = tc.CreditInvoiceDate;
number.CreditInvoiceNumber = tc.CreditInvoiceNumber;
number.CreditInvoiceAmount = tc.CreditInvoiceAmount;
number.CreditDeniedDate = tc.CreditDeniedDate;
number.CreditDeniedReasonId = tc.CreditDeniedReasonId;
number.CreditDeniedNotes = tc.CreditDeniedNotes;
}
entity.SaveChanges();
entity.Database.ExecuteSqlCommand("TRUNCATE TABLE TemporaryCsvUpload");
TempData["Success"] = "Updated Successfully";
}
catch (LINQtoCSVException)
{
TempData["Error"] = "Upload Error: Ensure you have the correct header fields and that the file is of .csv format.";
}
return View("Upload");
}
The issue in the above code is that tc is inside the first loop, but the matches are defined after the loop with var idMatches = entity.Authorizations.ToList().Where(x => x.Id == tc.Id);, so I am only getting the last item of the first loop.
So I would need to put var idMatches = entity.Authorizations.ToList().Where(x => x.Id == tc.Id); in the first loop, but then I can't access it in the second. If I nest the second loop then it is way to slow. Is there any way I could put the above statement in the first loop and still access it. Or any other ideas to accomplish the same thing? Thanks!
Instead of using multiple loops, keep track of processed IDs as you go and then exclude any duplicates.
[HttpPost]
public ActionResult UploadValidationTable(HttpPostedFileBase csvFile)
{
var inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
var cc = new CsvContext();
var filePath = uploadFile(csvFile.InputStream);
var model = cc.Read<Credit>(filePath, inputFileDescription);
try
{
var entity = new TestEntities();
var tcIdFound = new HashSet<string>();
foreach (var item in model)
{
if (tcIdFound.Contains(item.Id))
{
continue;
}
var tc = new TemporaryCsvUpload();
tc.Id = item.Id;
tc.CreditInvoiceAmount = item.CreditInvoiceAmount;
tc.CreditInvoiceDate = item.CreditInvoiceDate;
tc.CreditInvoiceNumber = item.CreditInvoiceNumber;
tc.CreditDeniedDate = item.CreditDeniedDate;
tc.CreditDeniedReasonId = item.CreditDeniedReasonId;
tc.CreditDeniedNotes = item.CreditDeniedNotes;
entity.TemporaryCsvUploads.Add(tc);
}
entity.SaveChanges();
entity.Database.ExecuteSqlCommand("TRUNCATE TABLE TemporaryCsvUpload");
TempData["Success"] = "Updated Successfully";
}
catch (LINQtoCSVException)
{
TempData["Error"] = "Upload Error: Ensure you have the correct header fields and that the file is of .csv format.";
}
return View("Upload");
}
If you want to make sure you get the last value for any duplicate ids, then store each TemporaryCsvUpload record in a dictionary instead of using only a HashSet. Same basic idea though.
Declare idMatches before the first loop, but don't instantiate it or set its value to null. Then you'll be able to use it inside both loops. After moving the declaration before the first loop, you'll still end up having the values from the last iteration using a simple Where. You'll need to concatenate the already existing list with results for the current iteration.

C# StreamReader failing to read lines

I am creating a banking program and I want to be able to read my text file of accounts and add them to a list. My problem is, it only reads 1 line and after that, it will get an error saying the line is null, but it shouldn't be because the second like should be the age.
I want it to continuously go through the accounts adding the data to the List, each account is separated by a blank line.
Code:
StreamReader FileToRead = new StreamReader(#"C:\Users\...\Accounts.txt");
Account NewAccount = new Account();
string line;
do
{
NewAccount.Name = FileToRead.ReadLine();
NewAccount.Age = int.Parse(FileToRead.ReadLine());
NewAccount.Balance = int.Parse(FileToRead.ReadLine());
NewAccount.Address.Country = FileToRead.ReadLine();
NewAccount.Address.City = FileToRead.ReadLine();
NewAccount.Address.FirstLine = FileToRead.ReadLine();
NewAccount.Address.SecondLine = FileToRead.ReadLine();
NewAccount.Address.PostCode = FileToRead.ReadLine();
NewAccount.AccountNumber = int.Parse(FileToRead.ReadLine());
Accounts.Add(NewAccount);
} while ((line = FileToRead.ReadLine()) != null);
Text file: http://pastebin.com/raw.php?i=1r9TEUPx
Well, the only real error I can see offhand is that you're not creating a new instance of Account - so what you'll be doing is changing the values on a single account and readding it to the list - you'll only end up with the last account in the file stored. You need to create a new Account for each iteration of the loop.
Tried with your file and the code fails at the second loop not the first one.
This because the "blankline" at the end triggers a second loop but then there is no more data to read.
If you are sure that every 'record' is separated by a blank line then you could simply add another read at the end of the loop
do
{
NewAccount = new Account();
NewAccount.Name = FileToRead.ReadLine();
NewAccount.Age = int.Parse(FileToRead.ReadLine());
NewAccount.Balance = int.Parse(FileToRead.ReadLine());
NewAccount.Address.Country = FileToRead.ReadLine();
NewAccount.Address.City = FileToRead.ReadLine();
NewAccount.Address.FirstLine = FileToRead.ReadLine();
NewAccount.Address.SecondLine = FileToRead.ReadLine();
NewAccount.Address.PostCode = FileToRead.ReadLine();
NewAccount.AccountNumber = int.Parse(FileToRead.ReadLine());
FileToRead.ReadLine(); // here to absorb the empty line between 'records'
Accounts.Add(NewAccount);
} while ((line = FileToRead.ReadLine()) != null);
Now when you reach the end-of-file the while loop exits correctly.....
EDIT: Seeing the answer from Eric -- Added the correct initialization of a new Account for every loop
Another way to do it:
string[] lines = System.IO.File.ReadAllLines(#"C:\Users\...\Accounts.txt");
if (lines != null && lines.Length > 0)
{
Account NewAccount = new Account();
NewAccount.Name = lines[0].ToString();
NewAccount.Age = lines[1].ToString();
NewAccount.Balance = lines[2].ToString();
NewAccount.Address.Country = lines[3].ToString();
NewAccount.Address.City = lines[4].ToString();
NewAccount.Address.FirstLine = lines[5].ToString();
NewAccount.Address.SecondLine = lines[6].ToString();
NewAccount.Address.PostCode = lines[7].ToString();
NewAccount.AccountNumber = lines[8].ToString();
Accounts.Add(NewAccount);
}
If you have such problems with the Streamreader, consider to use File.ReadAllLines instead:
var lines = File.ReadAllLines(path);
var NewAccount = new Account();
NewAccount.Name = lines.First();
NewAccount.Age = int.Parse(lines.ElementAt(1));
NewAccount.Balance = int.Parse(lines.ElementAt(2));
NewAccount.Address.Country = lines.ElementAt(3);
NewAccount.Address.City = lines.ElementAt(4);
NewAccount.Address.FirstLine = lines.ElementAt(5);
NewAccount.Address.SecondLine = lines.ElementAt(6);
NewAccount.Address.PostCode = lines.ElementAt(7);
NewAccount.AccountNumber = int.Parse(lines.ElementAt(8));
If your contains valid data your code probably throws exception on:
NewAccount.Address.Country = FileToRead.ReadLine();
It looks like you have some kind of class for address. You have to instantiate this property in Account constructor or in loop:
do
{
...
NewAccount.Balance = int.Parse(FileToRead.ReadLine());
NewAccount.Address = new Account.AddressClass();
NewAccount.Address.Country = FileToRead.ReadLine();
...
} while ((line = FileToRead.ReadLine()) != null);
I also assumed you previously instantiated other variables like
Account NewAccount = new Account();
List<Account> Accounts = new List<Account>();

how to get data from my List<T>

guyz i know how to add data from my list but the problem is how can i retrieve it...?
id delcared my list in GlobalVar.cs:
public static List<string> ViolationRefNumToPrint = new List<string>();
here's the code behind in adding data to my list.....
GlobalVar.ViolationRefNumToPrint.Clear();
for (int i = 0; i < lvviolations.Items.Count; i++)
{
GlobalVar.ViolationRefNumToPrint.Add(((EmpViolationObject)lvviolations.Items[i]).VioRefNum);
}
my question is how can i retrieve it to my list... :(
EDIT
guyz i've used the code below. which is given by #evanmcdonnal. actually i'm goin to use this on my report... and i've used DocumentViewer
here's my code....
ReportDocument reportDocument = new ReportDocument();
string ats = new DirectoryInfo(Environment.CurrentDirectory).Parent.Parent.FullName;
StreamReader reader = new StreamReader(new FileStream(ats.ToString() + #"\Template\ReportViolation.xaml", FileMode.Open, FileAccess.Read));
reportDocument.XamlData = reader.ReadToEnd();
reportDocument.XamlImagePath = Path.Combine(ats.ToString(), #"Template\");
reader.Close();
DateTime dateTimeStart = DateTime.Now; // start time measure here
List<ReportData> listData = new List<ReportData>();
int i = 0;
foreach (string item in GlobalVar.ViolationRefNumToPrint)
{
ReportData data = new ReportData();
data.ReportDocumentValues.Add("PrintDate", DateTime.Now);
data.ReportDocumentValues.Add("EmpIDNum", NewIDNumber.ToString());
data.ReportDocumentValues.Add("EmpName", NewEmpName.ToString());
data.ReportDocumentValues.Add("EmpPosition", NewPosition.ToString());
data.ReportDocumentValues.Add("PageNumber",(i + 1));
data.ReportDocumentValues.Add("PageCount", GlobalVar.ViolationRefNumToPrint.Count.ToString());
listData.Add(data);
i++;
}
XpsDocument xps = reportDocument.CreateXpsDocument(listData);
documentViewer.Document = xps.GetFixedDocumentSequence();
// show the elapsed time in window title
Title += " - generated in " + (DateTime.Now - dateTimeStart).TotalMilliseconds + "ms";
the problem here is it give's me error like this....
You have to loop over it and search for the item you want.
foreach (string item in ViolationRefNumToPrint)
{
Console.Out(item);
}
If instead you want a specific item (assume your list has objects call it string itemImLookinFor = "some nonsense"; loop over it with a conditional to match;
foreach (MyObject item in ViolationRefNumToPrint)
{
if (item.name == itemImLookinFor)
//do something with this object
}

Categories

Resources