Array to List<t> c# - c#

My current code works and output is correct. I am pulling data from a data.txt file and have successfully done so to an array using TextFieldParser. Is there a way to convert my code to a List? And how so? If converting is not an option then any recommendations on where to start with the code? Basically trying to go from an array to a list collections.
public partial class EmployeeInfoGeneratorForm : Form
{
public EmployeeInfoGeneratorForm()
{
InitializeComponent();
}
// button event handler
private void GenerateButton_Click(object sender, EventArgs e)
{
string[] parts;
if(File.Exists("..\\data.txt"))
{
TextFieldParser parser = new TextFieldParser("..\\data.txt");
parser.Delimiters = new string[] { "," };
while (true)
{
parts = parser.ReadFields();
if (parts == null)
{
break;
}
this.nameheadtxt.Text = parts[0];
this.addressheadtxt.Text = parts[1];
this.ageheadtxt.Text = parts[2];
this.payheadtxt.Text = parts[3];
this.idheadtxt.Text = parts[4];
this.devtypeheadtxt.Text = parts[5];
this.taxheadtxt.Text = parts[6];
this.emp1nametxt.Text = parts[7];
this.emp1addresstxt.Text = parts[8];
this.emp1agetxt.Text = parts[9];
this.emp1paytxt.Text = parts[10];
this.emp1idtxt.Text = parts[11];
this.emp1typetxt.Text = parts[12];
this.emp1taxtxt.Text = parts[13];
this.emp2nametxt.Text = parts[14];
this.emp2addresstxt.Text = parts[15];
this.emp2agetxt.Text = parts[16];
this.emp2paytxt.Text = parts[17];
this.emp2idtxt.Text = parts[18];
this.emp2typetxt.Text = parts[19];
this.emp2taxtxt.Text = parts[20];
this.emp3nametxt.Text = parts[21];
this.emp3addresstxt.Text = parts[22];
this.emp3agetxt.Text = parts[23];
this.emp3paytxt.Text = parts[24];
this.emp3idtxt.Text = parts[25];
this.emp3typetxt.Text = parts[26];
this.emp3taxtxt.Text = parts[27];
}
}
else //Error Message for if File isn't found
{
lblError.Text = "File Not Found";
}
}
}

In your code example there are two arrays.
First example
parser.Delimiters = new string[] { "," };
Since parser is a TextFieldParser, I can see that Delimiters must be set to a string array. So you cannot change it.
Second example
string[] parts;
parts = parser.ReadFields();
This array accepts the result of parser.ReadFields(). The output of that function is a string array, so this code can't be changed without breaking the call.
However, you can immediately convert it to a list afterward:
var parts = parser.ReadFields().ToList();
There isn't much point to this either.
An array is just as good as a list when the size of the array/list doesn't change after it is created. Making it into a list will just add overhead.

There are a number of problems here. I'd be inclined to write your code like this:
public static IEnumerable<List<string>> ParseFields(string file)
{
// Use "using" to clean up the parser.
using (var parser = new TextFieldParser(file))
{
parser.Delimiters = new string[] { "," };
// Use end-of-data, not checks for null.
while (!parser.EndOfData)
yield return parser.ReadFields().ToList();
}
}
I'd refactor your code to put the UI updates in one method:
private void UpdateText(List<string> parts ) { ... }
You only do something with the last element in the sequence; all your previous edits are lost. So be explicit about that:
private void GenerateButton_Click(object sender, EventArgs e)
{
// Use a named constant for constant strings used in several places
const string data = "..\\data.txt";
if(!File.Exists(data))
{
lblError.Text = "File Not Found";
} else {
var parts = ParseFields(data).LastOrDefault();
if (parts != null)
UpdateText(parts);
}
}
See how much cleaner that logic looks when you break it up into smaller parts? It's very pleasant to have methods that fit easily onto a page.

A direct answer to your question:
Use the List<T> constructor that takes an IEnumerable<T> parameter.
With that said, I would read Mr. Lippert's answer until you fully understand it.

Related

c# implement generic of T to avoid extra logic and have cleaner code

I have the following code using C# Winforms DataGridView.
// METHOD TO PASTE DATA INTO GRID
private void btnPasteCatalog_Click(object sender, EventArgs e)
{
BindingSource source = (BindingSource)gridCatalog.DataSource;
source.ListChanged -= Source_ListChanged;
IBindingList dataSource = null;
BindingList<Models.SetupArea> areaList = null;
BindingList<Models.SetupDepartment> deptList = null;
if (source.DataSource is BindingList<Models.SetupArea>)
{
dataSource = (BindingList<Models.SetupArea>)source.DataSource;
}
else if(source.DataSource is BindingList<Models.SetupDepartment>)
{
dataSource = (BindingList<Models.SetupDepartment>)source.DataSource;
}
DataObject o = (DataObject)Clipboard.GetDataObject();
if (o.GetDataPresent(DataFormats.StringFormat))
{
string[] pastedRows = Regex.Split(o.GetData(DataFormats.StringFormat).ToString().TrimEnd("\r\n".ToCharArray()), "\r");
int j = 0;
try { j = gridCatalog.CurrentRow.Index; } catch { }
foreach (string pastedRow in pastedRows)
{
if (source.DataSource is BindingList<Models.SetupArea>)
{
// i had to use this instead of dataSource because if not I get error
areaList = (BindingList<Models.SetupArea>)source.DataSource;
areaList.Add(new Models.SetupArea()
{
Description = pastedRow.Split(new char[] { '\t' })[0]
});
}
else if (source.DataSource is BindingList<Models.SetupDepartment>)
{
// i had to use this instead of dataSource because if not I get error
deptList = (BindingList<Models.SetupDepartment>)source.DataSource;
deptList.Add(new Models.SetupDepartment()
{
Description = pastedRow.Split(new char[] { '\t' })[0]
});
}
j++;
}
// HERE IS WANT TO PASS GENERIC OF T
// save to database
if (source.DataSource is BindingList<Models.SetupArea>)
{
// This is wrong it should be only Models.SetupArea
SaveCatalog<BindingList<Models.SetupArea>>(areaList);
}
// save to database
if (source.DataSource is BindingList<Models.SetupDepartment>)
{
// This is wrong it should be only Models.SetupArea
SaveCatalog<BindingList<Models.SetupDepartment>>(deptList);
}
source.ListChanged += Source_ListChanged;
}
}
// SAVE DATA INTO LITEDB DATABASE
private void SaveCatalog<T>(T data)
{
// Open database (or create if doesn't exist)
using (var db = new LiteDatabase(#"SoftCATCovid19.db"))
{
string tableName = null;
switch (cboCatalogs.Text.ToLower())
{
case "area":
tableName = "setup_area";
break;
case "department":
tableName = "setup_department";
break;
}
// T should be either Models.SetupArea or Models.SetupDepartment
// right now im receiving BindingList<Models.SetupDepartment> and this breaks the code.
var col = db.GetCollection<T>(tableName);
var records = data as BindingList<T>;
col.Insert(data);
}
}
I would like to use Generic of T instead of doing all my IF logic because at the end I could have here more than 10 catalogs and the code will be huge and not maintainable.
The more important thing is that Im using BindingList so in order to pass the Generic T to SaveCatalog method I have to pass the class type like Model.SetupArea or Model.SetupDepartment and that the parameter is also of this type otherwise the code breaks.
I would like to use Generic of T instead of doing all my IF logic because at the end I could have here more than 10 catalogs and the code will be huge and not maintainable.
For the IF's part you have to use a reflection with generics combination in order to make the method really generic way:
private static void AddBindingSource<T>(BindingSource source, string pastedRow)
{
if (source.DataSource is BindingList<T> areaList)
{
var data = pastedRow.Split(new char[] { '\t' }).FirstOrDefault();
areaList.Add((T)Activator.CreateInstance(typeof(T), data));
}
}
The more important thing is that Im using BindingList so in order to pass the Generic T to SaveCatalog method I have to pass the class type like Model.SetupArea or Model.SetupDepartment and that the parameter is also of this type otherwise the code breaks.
AddBindingSource<Models.SetupArea>(new BindingSource(), "test");
Hope this helps ))
May be you just need to redefine SaveCatalog as follow:
// SAVE DATA INTO LITEDB DATABASE
private void SaveCatalog<T>(BindingList<T> data)
{
// Open database (or create if doesn't exist)
using (var db = new LiteDatabase(#"SoftCATCovid19.db"))
{
string tableName = GetTableName(data);
var col = db.GetCollection<T>(tableName);
foreach(var entry in data)
{
col.Insert(entry);
}
}
}
string GetTableName<T>(BindingList<T> data)
{
switch (data)
{
case BindingList<Models.SetupArea> _:
return "setup_area";
case BindingList<Models.SetupDepartment> _:
return "setup_department";
default:
return null;
}
}
Then you don't need to do the if anymore and only call SaveCatalog as follow:
SaveCatalog<Models.SetupArea>(areaList);
SaveCatalog<Models.SetupDepartment>(deptList);

Is it possible in C# to return a array back to the calling program?

Is it possible in C# to return a array back to the calling program? If it is not possible, please say it is not all possible. Another alternative is to create a long string and use string.split(). But that does not look nice.
ExamnationOfReturnsFiled("ABCDE1234E") //Calling program.
public yearsfiled[] ExamnationOfReturnsFiled(string panreceived) //function.
{
int k = 0; //to increment the array element.
string item = panreceived; //string value received call program.
string[] yearsfiled = new string[20];//Declaring a string array.
Regex year = new Regex(#"[0-9]{4}-[0-9]{2}");//to capture 2012-13 like entries.
using (StreamReader Reader1 = new StreamReader(#"C: \Users\Unnikrishnan C\Documents\Combined_Blue_Book.txt"))
{
Regex tofindpan = new Regex(item);//Regular Expression to catch the string from the text file being read.
bool tosearch = false;
Regex blank = new Regex(#"^\s*$"); //to detect a blank line.
while ((str.line1 = Reader1.ReadLine()) != null)
{
Match Tofindpan = tofindpan.Match(#"[A-Z]{5}[0-9]{4}[A-Z]{1}");
Match Blank = blank.Match(line1);
if (Blank.Success)
{
tosearch = false;
}
if (Tofindpan.Success)
{
tosearch = true; //when true the
}
if (tosearch == true)
{
Match Year = year.Match(str.line1);
if (Year.Success)
{
yearsfiled[k] = Year.Value;
k++;
}
}
}
return yearsfiled;
}
}
public string[] ExamnationOfReturnsFiled(string panreceived) //function
you are returning type not variable name change the method signature like above
You should be returning a string[]. Your return type yearsfiled[] is a variable name, not a type name
//from calling programme. Tested and succeeded.
string[] yearsfiled = new string[20];
yearsfiled = ExamnationOfReturnsFiled(item1);
// the function name modified as follows.
public static string[] ExamnationOfReturnsFiled(string panreceived)
{
Everything else as in the original post.
}
//It was tested. And found successful. Thanks so much to #Midhun Mundayadan and #Eavidan.

Pull separate columns from .csv into separate arrays in c#

Background on this project. It started as a simple homework assignment that required me to store 5 zip codes and their corresponding cities. When a user puts a Zip code in a textbox, a corresponding city is returned, and likewise the opposite can be done. I wrote the code to return these values, but then I decided I wanted to store ALL zip codes and their corresponding Cities in an external .csv, and store those values in arrays and run the code off that because if its worth doing, its worth overdoing! To clarify, this is no longer for homework, just to learn more about using external files in C#.
In the following code, I have called to open the file successfully, now I just need help in figuring out how to pull the data that is stored in two separate columns (one for city, one for zip code) and store them in two arrays to be acted upon by the for loop. Here is the code I have now. You can see how I have previously stored the other values in arrays and pulled them out:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnConvert2City_Click(object sender, EventArgs e)
{
try
{
string dir = System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().Location);
string path = dir + #"\zip_code_database_edited.csv";
var open = new StreamReader(File.OpenRead(path));
int EnteredZipcode = Convert.ToInt32(txtZipcode.Text.Trim());
string result = "No Cities Found";
string[] Cities = new String[5] { "FLINTSTONE", "JAMAICA", "SCHENECTADY", "COTTONDALE", "CINCINNATI" };
int[] Zipcode = new int[5] { 30725, 11432, 12345, 35453, 45263 };
for (int i = 0; i <= Zipcode.Length - 1; i++)
{
if (Zipcode[i] == EnteredZipcode)
{
result = Cities[i];
break;
}
}
string DisplayState = result;
txtCity.Text = DisplayState;
}
catch (FormatException)
{
MessageBox.Show("Input must be numeric value.");
}
catch (OverflowException)
{
MessageBox.Show("Zipcode to long. Please Re-enter");
}
}
private void btnConvert2Zipcode_Click(object sender, EventArgs e)
{
string dir = System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().Location);
string path = dir + #"\zip_code_database_edited.csv";
var open = new StreamReader(File.OpenRead(path));
String EnteredCity = txtCity.Text.ToUpper();
string result = "No Zipcode Found";
string[] Cities = new String[5] { "FLINTSTONE", "JAMAICA", "SCHENECTADY", "COTTONDALE", "CINCINNATI" };
int[] Zipcode = new int[5] { 30725, 11432, 12345, 35453, 45263 };
for (int i = 0; i <= Cities.Length - 1; i++)
{
if (Cities[i] == EnteredCity)
{
result = Convert.ToString(Zipcode[i]);
break;
}
}
string DisplayZip = result;
txtZipcode.Text = DisplayZip;
}
}
The following data is a snippet of what the data in my excel .csv looks like:
zip,primary_city
44273,Seville
44274,Sharon Center
44275,Spencer
44276,Sterling
44278,Tallmadge
44280,Valley City
44281,Wadsworth
44282,Wadsworth
44285,Wayland
And so on for about 46,000 rows.
How can I pull the zip and the primary_city into two separate arrays (I'm guessing with some ".Split "," "line) that my for-loop can operate on?
Also, if there are better ways to go about this, please let me know (but be sure to leave an explanation as I want to understand where you are coming from).
Don't create two separate array.Create a separate class for city
class City
{
public string Name{get;set;}
public int ZipCode{get;set;}
}
Now to read the data from that csv file
List<City> cities=File.ReadAllLines(path)
.Select(x=>new City
{
ZipCode=int.Parse(x.Split(',')[0]),
Name=x.Split(',')[1]
}).ToList<City>();
Or you can do this
List<City> cities=new List<City>();
foreach(String s in File.ReadAllLines(path))
{
City temp=new City();
temp.ZipCode=int.Parse(s.Split(',')[0]);
temp.Name=s.Split(',')[1];
cities.Add(temp);
}
You can try this:
string dir = System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().Location);
string path = dir + #"\zip_code_database_edited.csv";
var open = new StreamReader(File.OpenRead(path));
var cities = new HashList<string>();
var zipCodes = new HashList<int>();
var zipAndCity = new string[2];
string line = string.Empty;
using (open)
{
while ((line = reader.ReadLine()) != null)
{
zipAndCity = line.Split(",");
zipCodes.Add(int.Parse(zipAndCity[0]));
cities.Add(zipAndCity[1]);
}
}
I am posting this answer having learned much more about C# since I posted this question. When reading a CSV, there are better options than String.Split().
The .NET Framework already has a built-in dedicated CSV parser called TextFieldParser.
It's located in the Microsoft.VisualBasic.FileIO namespace.
Not only are there many edge cases that String.Split() is not properly equipped to handle, but it's also much slower to use StreamReader.

C# Creating a datasource that gets all branches for a specific bank

I am trying to create a binding source to my binding navigator that will be able to show all branches within a specific bank.
The statement that gets the datasource is as follows
branchMasterBindingSource.DataSource = Program.Kernel.Get<IBranchMasterService>().GetAllBranchMasters();
However, this pulls all branches regardless of the banks they belong to.
I need to know how to change this so that it gets AllBranchMasters where a field in the database ("U_bank_code") is equals to a combobox named "cb_bank_code"
Extra code is below:
private void cb_bank_code_SelectedIndexChanged(object sender, EventArgs e)
{
branchMasterBindingSource.DataSource = null;
branchMasterBindingSource.DataSource = Program.Kernel.Get<IBranchMasterService>().GetAllBranchMasters();
//clear textfields after input
lbl_show_bank_name.Text = string.Empty;
txt_branch_code.Text = string.Empty;
txt_branch_name.Text = string.Empty;
txt_swift_sort_code.Text = string.Empty;
txt_address_1.Text = string.Empty;
txt_address_2.Text = string.Empty;
txt_comments.Text = string.Empty;
var bankMasterService = Program.Kernel.Get<IBankMasterService>();
var bankMasters = from bm in bankMasterService.GetAllBankMasters()
where bm.U_Bank_code.Trim().Equals(cb_bank_code.Text.Trim(), StringComparison.CurrentCultureIgnoreCase)
select bm;
if (bankMasters.Any(x => x != null))
{
var bankMaster = bankMasters.First();
lbl_show_bank_name.Text = bankMaster.U_Bank_name;
CbBankCode = bankMaster.U_Bank_code;
}
else
{
//clear textfields after input
lbl_show_bank_name.Text = string.Empty;
}
Im new to C# and dot net and do not know how the syntax to change the statement. Any help appreciated
If I understand you correctly, and make a couple assumptions on the types, the following should work:
branchMasterBindingSource.DataSource = Program.Kernel.Get<IBranchMasterService>().GetAllBranchMasters.Where(x => x.U_Bank_code.Trim().Equals(cb_bank_code.Text.Trim(), StringComparison.CurrentCultureIgnoreCase))
I'm guessing the
Program.Kernel.Get<IBranchMasterService>().GetAllBranchMasters
returns an IEnumerable or perhaps List. There won't be much you can do about that function returning all banks/branches unless you either:
Change GetAllBranchMasters to return an IQueryable - you can then apply the filter as above and add .ToList() to the end to perform the query.
(Recommended) Create a second method that accepts a Bank Code, and returns the branches in accordance with the underlying DAL.
Although you should look at refactoring your data access methods to do the filtering something like this should do the trick for you
private void cb_bank_code_SelectedIndexChanged(object sender, EventArgs e)
{
ClearTextfieldsAfterInput();
branchMasterBindingSource.DataSource = GetSelectedBranchMasters();
var bankMasters = GetSelectedBankMaster();
if (bankMasters.Any(x => x != null))
{
var bankMaster = bankMasters.First();
lbl_show_bank_name.Text = bankMaster.U_Bank_name;
CbBankCode = bankMaster.U_Bank_code;
}
else
{
//clear textfields after input
lbl_show_bank_name.Text = string.Empty;
}
}
private IEnumerable<BankMaster> GetSelectedBankMaster()
{
var selectedBank = cb_bank_code.Text.Trim();
return Program.Kernel.Get<IBankMasterService>()
.GetAllBankMasters()
.Where(bm => bm.U_Bank_code.Trim().Equals(selectedBank, StringComparison.CurrentCultureIgnoreCase))
.ToList();
}
private IEnumerable<BranchMaster> GetSelectedBranchMasters()
{
var selectedBank = cb_bank_code.Text.Trim();
return Program.Kernel.Get<IBranchMasterService>()
.GetAllBranchMasters()
.Where(branch => string.Equals(branch.U_bank_code, selectedBank, StringComparison.CurrentCultureIgnoreCase))
.ToList();
}
private void ClearTextfieldsAfterInput()
{
lbl_show_bank_name.Text = "";
txt_branch_code.Text = "";
txt_branch_name.Text = "";
txt_swift_sort_code.Text = "";
txt_address_1.Text = "";
txt_address_2.Text = "";
txt_comments.Text = "";
}

C#: What's an efficient way of parsing a string with one delimiter through ReadLine() of TextReader?

C#: What's an efficient way to parse a string with one delimiter for each ReadLine() of TextReader?
My objective is to load a list of proxies to ListView into two columns (Proxy|Port) reading from a .txt file. How would I go upon splitting each readline() into the proxy and port variables with the delimiter ":"?
This is what I've got so far,
public void loadProxies(string FilePath)
{
string Proxy; // example/temporary place holders
int Port; // updated at each readline() loop.
using (TextReader textReader = new StreamReader(FilePath))
{
string Line;
while ((Line = textReader.ReadLine()) != null)
{
// How would I go about directing which string to return whether
// what's to the left of the delimiter : or to the right?
//Proxy = Line.Split(':');
//Port = Line.Split(':');
// listview stuff done here (this part I'm familiar with already)
}
}
}
If not, is there a more efficient way to do this?
string [] parts = line.Split(':');
string proxy = parts[0];
string port = parts[1];
You could split them this way:
string line;
string[] tokens;
while ((Line = textReader.ReadLine()) != null)
{
tokens = line.Split(':');
proxy = tokens[0];
port = tokens[1];
// listview stuff done here (this part I'm familiar with already)
}
it's best practise to use small letter names for variables in C#, as the other ones are reserved for class / namespace names etc.
How about running a Regex on the whole file?
var parts=
Regex.Matches(input, #"(?<left>[^:]*):(?<right>.*)",RegexOptions.Multiline)
.Cast<Match>()
.Where(m=>m.Success)
.Select(m => new
{
left = m.Groups["left"],
right = m.Groups["right"]
});
foreach(var part in parts)
{
//part.left
//part.right
}
Or, if it's too big, why not Linqify the ReadLine operation with yielding method?
static IEnumerable<string> Lines(string filename)
{
using (var sr = new StreamReader(filename))
{
while (!sr.EndOfStream)
{
yield return sr.ReadLine();
}
}
}
And run it like so:
var parts=Lines(filename)
.Select(
line=>Regex.Match(input, #"(?<left>[^:]*):(?<right>.*)")
)
.Where(m=>m.Success)
.Select(m => new
{
left = m.Groups["left"],
right = m.Groups["right"]
});
foreach(var part in parts)
{
//part.left
//part.right
}
In terms of efficiency I expect you'd be hard-pressed to beat:
int index = line.IndexOf(':');
if (index < 0) throw new InvalidOperationException();
Proxy = line.Substring(0, index);
Port = int.Parse(line.Substring(index + 1));
This avoids the array construction / allocation associated with Split, and only looks as far as the first delimited. But I should stress that this is unlikely to be a genuine performance bottleneck unless the data volume is huge, so pretty-much any approach should be fine. In fact, perhaps the most important thing (I've been reminded by the comment below) is to suspend the UI while adding:
myListView.BeginUpdate();
try {
// TODO: add all the items here
} finally {
myListView.EndUpdate();
}
You might want to try something like this.
var items = File.ReadAllText(FilePath)
.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)
.Select(line => line.Split(':'))
.Select(pieces => new {
Proxy = pieces[0],
Port = int.Parse(pieces[1])
});
If you know that you won't have a stray newline at the end of the file you can do this.
var items = File.ReadAllLines(FilePath)
.Select(line => line.Split(':'))
.Select(pieces => new {
Proxy = pieces[0],
Port = Convert.ToInt32(pieces[1])
});

Categories