Related
I am making a web service for an app with Tesseract Ocr 3.02.
I want to create variables on depending of how many informations I get on the business card and after that classify information from a string.
For example:
Tel. +496123456789$Mobil +49123456789$kai.kalsbach#gmail.com$www.google.com$Kai Kalsbach$Muster Str 1a$40599 Düsseldorf$"
And then like this:
-Telephone number
-First Name
-Last Name
-Email
-Address
That was my first idea:
string endText1 = text.Split('$')[0];
string endText2 = text.Split('$')[1];
string endText3 = text.Split('$')[2];
string endText4 = text.Split('$')[3];
string endText5 = text.Split('$')[4];
string endText6 = text.Split('$')[5];
string endText7 = text.Split('$')[6];
string endText8 = text.Split('$')[7];
and after that i would classify the variables.
but in many cases I get the following exception because the number of informations can vary depending of business card.
System.IndexOutOfRangeException: Index was outside the bounds of the array c#
The IndexOutOfRangeException exception is thrown because the code tries to access an item outside the array length.
My proposition : I created formattedArray with contains always 8 items and I copied the splited array to this formattedArray. With that, you have no more IndexOutOfRangeException because the item missing in text.Split('$') is null in formattedArray
var a = text.Split('$');
var formattedArray = new string[8];
Array.Copy(a, formattedArray, a.Length);
string endText1 = formattedArray [0];
string endText2 = formattedArray [1];
string endText3 = formattedArray [2];
string endText4 = formattedArray [3];
string endText5 = formattedArray [4];
string endText6 = formattedArray [5];
string endText7 = formattedArray [6];
string endText8 = formattedArray [7];
string[] Splitted = text.Split('$');
And you mentioned you want to make a decision based on the number of elements the split spits out
int Count = Splitted.Length;
switch(Count)
{ case 0: //DoStuff
break;
....
default:
break;
}
In your case, it is better to use the following:
string[] stringList = text.Split('$');
foreach(string val in stringList)
{
//your logic.
}
You can split the string once using the .Split method.
Then afterwards run it in a foreach or for loop. I believe your logic is based on the amount of strings, so you are looking for a 'for' loop.
string[] split = text.Split('$');
for (int i = 0; i < split.Length; i++)
{
var text = split[i];
// Your logic here...
switch (i) // for logic based on the index of the string
{
case 0:
// do something
break;
case 1:
// do something
break;
}
}
The IndexOutOfRangeException exception is thrown because the code tries to access the 8th item in a 7-item array :
string endText8 = text.Split('$')[7];
Indexes in .NET collections are 0-based which means 7 refers to the 8th element.
By default, String.Split will return empty fields as well. This means that either the string isn't the same as the one posted here, or that the StringSplitOptions.RemoveEmptyEntries was used
String.Split returns a string array that can be stored in a string[] variable. There's no need to repeat String.Split, or use multiple variables :
var items = text.Split(new[]{'$'},StringSplitOptions.RemoveEmptyEntries);
Creating a class from this array is simple enough that you probably don't need to create a custom parser :
class Record
{
public string Telephone {get;set;}
...
}
var items = text.Split('$');
var record=new Record
{
Telephone=items[0],
Mobile=items[1],
...
};
Another easy way to do that is to use a try, then all variables will be created until the index has reached its maximum.
string[] strArray = text.Split('$');
Try {
string endText1 = strArray[0];
string endText2 = strArray[1];
string endText3 = strArray[2];
string endText4 = strArray[3];
string endText5 = strArray[4];
string endText6 = strArray[5];
string endText7 = strArray[6];
string endText8 = strArray[7];
}
catch
{
//nothing
}
Create factory and recognizers
public class PhoneItem : IItem
{
public PhoneItem(string text)
{
// some code
}
}
public interface IRecognizer
{
IItem Recognize(int index, string text);
}
public class PhoneRecognizer : IRecognizer
{
public IItem Recognize(int index, string text)
{
return index == 0 ? new PhoneItem(text) : null;
}
}
public class ItemFactory
{
private IEnumerable<IRecognizer> _recognizers = new []
{
new PhoneRecognizer(),
new FullNameRecognizer()
};
public IItem CreateItem(int index, string text)
{
foreach (var rec in _recognizers)
{
var item = rec.Recognize(index, text);
if (item != null)
{
return item;
}
}
throw new Exception("Item not recognized");
}
}
Split string to pieces
var parts = text.Split('$');
Use the factory to create objects
var factory = new ItemFactory();
var items = new List<IItem>();
for (int i = 0; i < parts.Length; i++)
{
items.Add(factory.CreateItem(i, parts[i]));
}
// do whatever you wants
My aim is to generate a string. which has following conditions.
First i will take an empty string and start building the string based on length.
Example:
I have an empty string "".
In the first step i want to add a string till 8 characters, means first my string is "" then till 8 characters my string should contain the value Hiwor so finally my string will be Hiwor if there is no value empty value should be padded in the string.
In the second step i want to add the string meena till 10 positions , so my final string should be Hiwor meena. In this way i want to build my string. Ho can i achieve this. Can you please help me.
Sample:
initial string ""
first step adding string Hiwor till 8 positions,
so final string should be Hiwor
second step adding string meena till 10 postions
so final string should be Hiwor meena .
Till now i tried like this
Dictionary<string, Int16> transLine = new Dictionary<string, Int16>();
transLine.Add("ProductCode", 1);
transLine.Add("ApplicantFirstName", 12);
transLine.Add("ApplicantMiddleInitial", 1);
transLine.Add("partner", 1);
transLine.Add("employee", 8);
List<string> list = new List<string>();
list.Add("grouplife");
list.Add("meena");
list.Add("d");
list.Add("yes");
list.Add("yes");
StringBuilder sb = new StringBuilder();
foreach (var listItem in list)
{
foreach (var item in transLine)
{
if (listItem == item.Key)
{
var length = item.Value;
sb.Insert(length, item.Key);
}
}
}
but it is throwing me an exception.Index was out of range. Must be non-negative and less than the size of the collection.
Firstly define an extension method for StringBuilder:
public static class StringBuilderExtensions
{
public static void AppendPadded(this StringBuilder builder, string value, int length);
{
builder.Append($"{value}{new string(' ', length)}".Substring(0, length));
}
public static void AppendPadded(this StringBuilder builder, int value, int length);
{
builder.Append($"{new string('0', length)}{value}".Reverse().ToString().Substring(0, length).Reverse().ToString());
}
}
Then use:
StringBuilder builder = new StringBuilder();
builder.AppendPadded("Hiwor", 8);
builder.AppendPadded("meena", 10);
return builder.ToString();
Or with your example:
foreach (string item in list)
builder.AppendPadded(item, transLine[item]);
EDIT: Ok, so looks like you want to be able to define a format then build the string using it. Try:
(you will need to reference System.ComponentModel.DataAnnotations and System.Reflection for this)
public abstract class AnItem
{
private static int GetLength(PropertyInfo property)
{
StringLengthAttribute attribute = property.GetCustomAttributes(typeof(StringLengthAttribute), true).FirstOrDefault() as StringLengthAttribute;
if (attribute == null)
throw new Exception($"StringLength not specified for {property.Name}");
return attribute.MaxLength();
}
private string GetPropertyValue(PropertyInfo property)
{
if (property.PropertyType == typeof(string))
return property.GetValue(this);
else if (property.PropertyType == typeof(int))
return property.GetValue(this).ToString();
else
throw new Exception($"Property '{property.Name}' is not a supported type");
}
private static void SetPropertyValue(object item, PropertyInfo property, string value)
{
if (property.PropertyType == typeof(string))
property.SetValue(item, value, null);
else if (property.PropertyType == typeof(int))
property.SetValue(item, int.Parse(value), null);
else
throw new Exception($"Property '{property.Name}' is not a supported type");
}
public string GetPaddedString()
{
StringBuilder builder = new StringBuilder();
PropertyInfo[] properties = GetType().GetProperties();
foreach (PropertyInfo property in properties)
builder.AppendPadded(GetPropertyValue(property), GetLength(property));
return builder.ToString();
}
public static T CreateFromPaddedString<T>(string paddedString) where T : AnItem, new()
{
T item = new T();
int offset = 0;
PropertyInfo[] properties = typeof(T).GetProperties();
foreach (PropertyInfo property in properties)
{
int length = GetLength(property);
if (offset + length > paddedString.Length)
throw new Exception("The string is too short");
SetPropertyValue(item, property, paddedString.Substring(offset, length)));
offset += length;
}
if (offset < paddedString.Length)
throw new Exception("The string is too long");
return item;
}
}
public class MyItem : AnItem
{
[StringLength(1)]
public string ProductCode { get; set; }
[StringLength(12)]
public string ApplicantFirstName { get; set; }
[StringLength(1)]
public string ApplicantMiddleInitial { get; set; }
[StringLength(1)]
public string Partner { get; set; }
[StringLength(8)]
public string Employee { get; set; }
}
Then use it:
MyItem item = new MyItem
{
ProductCode = "grouplife",
ApplicantFirstName = "meena",
ApplicantMiddleInitial = "d",
Partner = "yes",
Employee = "yes"
};
string paddedString = item.GetPaddedString();
And to read a string to get an item:
MyItem item = AnItem.CreateFromPaddedString<MyItem>(paddedString);
At first I want to say something more about your exception:
Index was out of range. Must be non-negative and less than the size of the collection.
As the exception already said. The problem is that you want to access a position within your new StringBuilder sb which does not exists.
StringBuilder sb = new StringBuilder();
After this line your new sb is empty. There is no single character in it. So you can only access the index at position 0. But almost in your first iteration of the inner for-each loop you want to target the index 1 and try to insert your string at the position 1, which does not exists.
// length: 1 and item.Key: ProductCode
sb.Insert(length, item.Key);
So how to solve this. You can use a feature from String.Format() or since C#6 the string interpolation.
So for example:
String.Format()
var sb = new StringBuilder(string.Empty); // sb: []
sb.Append(string.Format("{0, -8}", "Hiwor")); // sb: [Hiwor ]
sb.Append(string.Format("{0,-10}", "meena")); // sb: [Hiwor meena ]
C#6 String Interpolation
var sb = new StringBuilder(string.Empty); // sb: []
sb.Append($"{"Hiwor", -8}"); // sb: [Hiwor ]
sb.Append($"{"meena", -10}"); // sb: [Hiwor meena ]
// ...
Targeting your edit:
With your given list items you will never get a match with any of your dictionary keys.
I am trying to display my results as follows:
-.-|- [tab] kt
-.|-- [tab] nm
-.|-|- [tab] ntt
But this is my current output
-.-|-| kt
-.|--| nm
-.|-|-| [tab]ntt
There is a | at the end of every Morse code which I would like to remove since it is at the end.
Also because the user can input Morse code with space between dots and dashes - i noticed that it affects the alignment of the characters and not all of them get tabbed properly. The word tab isn't supposed to show i just wrote it in because I didn't know how to place a real tab.
private static readonly IDictionary<char, string> morseCode_alpha = new Dictionary<char, string>
{
{'a', ".-"},{'b',"-..."}, {'c',"-.-."}, {'d',"-.."}, {'e',"."},
{'f',"..-."}, {'g',"--."}, {'h',"...."},{'i',".."}, {'j',".---"},
{'k',"-.-"}, {'l',".-.."}, {'m',"--"}, {'n',"-."}, {'o',"---"},
{'p',".--."}, {'q',"--.-"}, {'r',".-."}, {'s',"..."}, {'t',"-"},
{'u',"..-"}, {'v',"...-"}, {'w',".--"}, {'x',"-..-"}, {'y',"-.--"}, {'z',"--.."}
};
private static string ConvertMorseToText(string symbolCode)
{
var builder = new StringBuilder(4 * symbolCode.Length);
foreach (char c in symbolCode)
builder.Append(morseCode_alpha[c]);
return builder.ToString();
}
private static string ConvertTextToMorse(char ch)
{
if (morseCode_alpha.Keys.Contains(ch))
return morseCode_alpha[ch];
else
return string.Empty;
}
private static string ConvertStringToMorse(string letters)
{
StringBuilder sb = new StringBuilder();
foreach (char ch in letters)
{
if (sb.Length != 0 && sb[sb.Length - 1] != ' ')
sb.Append("|");
sb.Append(ConvertTextToMorse(ch));
}
return sb.ToString();
}
private static IEnumerable<string> Permutations( string symbolCode)
{
int n = symbolCode.Length;
if (n == 0 || symbolCode.Length == 0)
yield return " ";
else
foreach (var entry in morseCode_alpha)
if (symbolCode.StartsWith(entry.Value))
foreach (string next in Permutations(symbolCode.Substring(entry.Value.Length)))
yield return entry.Key + next;
}
private static void Write( string rest)
{
string result = ConvertStringToMorse(rest);
Console.Write(result+"\t");
Console.WriteLine(rest);
}
static void Main(string[] args)
{
string morseInput;
string entered = "";
do
{
Console.WriteLine("Enter Morse Code: \n");
morseInput = Console.ReadLine().Replace(" ","");
bool isValid = Regex.IsMatch(morseInput, #"^[-.]+$");
if (isValid)
{
Console.WriteLine("\nAll permutations:\n");
string morse = ConvertMorseToText(entered);
string permutations = morseInput.Substring(morse.Length);
Write(permutations);
var nexts = new List<string>(Permutations(permutations));
foreach (string next in nexts)
Write(next);
}
else
{
Console.WriteLine("\nFormat of morse must be only dots and dashes.");
Console.WriteLine("Parameter name: "+morseInput+"\n");
}
}
while (morseInput.Length != 0);
}
And, to answer the other part of the question...
Tabstops are fixed for console writing, so it would be better to use something like String.PadRight
so, your code could be:
private static void Write(string rest)
{
string result = ConvertStringToMorse(rest);
Console.Write(result.PadRight(20));
Console.WriteLine(rest);
}
Draft version of the method:
private static string ConvertStringToMorse(string letters)
{
var result = string.Join("|",
letters
.Select(ConvertTextToMorse)
.Where(morse => !string.IsNullOrEmpty(morse)));
return result;
}
Update:
Please note that the entered variable is used only once: when defined - empty string is assigned. Then the ConvertMorseToText(entered) method is called: it always returns empty string for the empty string argument. After this assignment string permutations = morseInput.Substring(morse.Length); the permutations variable will store exactly the same value as morse variable (because morse.Length is always 0).
So, it seems that the entered variable and the ConvertMorseToText() method are useless (both can be safely removed):
static void Main(string[] args)
{
do
{
Console.WriteLine("Enter Morse Code: ");
string morseInput = Console.ReadLine();
if (string.IsNullOrWhiteSpace(morseInput))
{
// Empty or consists only of white-space characters
break;
}
morseInput = morseInput.Replace(" ", "");
bool isValid = Regex.IsMatch(morseInput, #"^[-.]+$");
if (isValid)
{
Console.WriteLine("All permutations:");
Console.WriteLine();
var nexts = Permutations(morseInput).ToList();
foreach (string next in nexts)
Write(next);
}
else
{
Console.WriteLine();
Console.WriteLine("Format of morse must be only dots and dashes.");
Console.WriteLine("Parameter name: {0}", morseInput);
}
}
while (true);
}
Update 2:
Consider using TryGetValue() method of Dictionary<TKey, TValue> instead of Keys.Contains and [] (indexer) i.e. do not perform look-up twice:
private static string ConvertTextToMorse(char ch)
{
string result;
return morseCode_alpha.TryGetValue(ch, out result) ? result : string.Empty;
}
Instead this code:
Console.Write(result+"\t");
Console.WriteLine(rest);
Use
Console.WriteLine("{0,-10}{1,-10}", result, rest);
Then you will see two columns (each max 10 charachters) with left alignment. Or remove "-" sign if you want right alignment.
Building a string for post request in the following way,
var itemsToAdd = sl.SelProds.ToList();
if (sl.SelProds.Count() != 0)
{
foreach (var item in itemsToAdd)
{
paramstr = paramstr + string.Format("productID={0}&", item.prodID.ToString());
}
}
after I get resulting paramstr, I need to delete last character & in it
How to delete last character in a string using C#?
Personally I would go with Rob's suggestion, but if you want to remove one (or more) specific trailing character(s) you can use TrimEnd. E.g.
paramstr = paramstr.TrimEnd('&');
build it with string.Join instead:
var parameters = sl.SelProds.Select(x=>"productID="+x.prodID).ToArray();
paramstr = string.Join("&", parameters);
string.Join takes a seperator ("&") and and array of strings (parameters), and inserts the seperator between each element of the array.
string source;
// source gets initialized
string dest;
if (source.Length > 0)
{
dest = source.Substring(0, source.Length - 1);
}
Try this:
paramstr.Remove((paramstr.Length-1),1);
I would just not add it in the first place:
var sb = new StringBuilder();
bool first = true;
foreach (var foo in items) {
if (first)
first = false;
else
sb.Append('&');
// for example:
var escapedValue = System.Web.HttpUtility.UrlEncode(foo);
sb.Append(key).Append('=').Append(escapedValue);
}
var s = sb.ToString();
string str="This is test string.";
str=str.Remove(str.Length-1);
It's better if you use string.Join.
class Product
{
public int ProductID { get; set; }
}
static void Main(string[] args)
{
List<Product> products = new List<Product>()
{
new Product { ProductID = 1 },
new Product { ProductID = 2 },
new Product { ProductID = 3 }
};
string theURL = string.Join("&", products.Select(p => string.Format("productID={0}", p.ProductID)));
Console.WriteLine(theURL);
}
It's good practice to use a StringBuilder when concatenating a lot of strings and you can then use the Remove method to get rid of the final character.
StringBuilder paramBuilder = new StringBuilder();
foreach (var item in itemsToAdd)
{
paramBuilder.AppendFormat(("productID={0}&", item.prodID.ToString());
}
if (paramBuilder.Length > 1)
paramBuilder.Remove(paramBuilder.Length-1, 1);
string s = paramBuilder.ToString();
paramstr.Remove((paramstr.Length-1),1);
This does work to remove a single character from the end of a string. But if I use it to remove, say, 4 characters, this doesn't work:
paramstr.Remove((paramstr.Length-4),1);
As an alternative, I have used this approach instead:
DateFrom = DateFrom.Substring(0, DateFrom.Length-4);
Add a StringBuilder extension method.
public static StringBuilder RemoveLast(this StringBuilder sb, string value)
{
if(sb.Length < 1) return sb;
sb.Remove(sb.ToString().LastIndexOf(value), value.Length);
return sb;
}
then use:
yourStringBuilder.RemoveLast(",");
Typical way of creating a CSV string (pseudocode):
Create a CSV container object (like a StringBuilder in C#).
Loop through the strings you want to add appending a comma after each one.
After the loop, remove that last superfluous comma.
Code sample:
public string ReturnAsCSV(ContactList contactList)
{
StringBuilder sb = new StringBuilder();
foreach (Contact c in contactList)
{
sb.Append(c.Name + ",");
}
sb.Remove(sb.Length - 1, 1);
//sb.Replace(",", "", sb.Length - 1, 1)
return sb.ToString();
}
I like the idea of adding the comma by checking if the container is empty, but doesn't that mean more processing as it needs to check the length of the string on each occurrence?
I feel that there should be an easier/cleaner/more efficient way of removing that last comma. Any ideas?
You could use LINQ to Objects:
string [] strings = contactList.Select(c => c.Name).ToArray();
string csv = string.Join(",", strings);
Obviously that could all be done in one line, but it's a bit clearer on two.
Your code not really compliant with full CSV format. If you are just generating CSV from data that has no commas, leading/trailing spaces, tabs, newlines or quotes, it should be fine. However, in most real-world data-exchange scenarios, you do need the full imlementation.
For generation to proper CSV, you can use this:
public static String EncodeCsvLine(params String[] fields)
{
StringBuilder line = new StringBuilder();
for (int i = 0; i < fields.Length; i++)
{
if (i > 0)
{
line.Append(DelimiterChar);
}
String csvField = EncodeCsvField(fields[i]);
line.Append(csvField);
}
return line.ToString();
}
static String EncodeCsvField(String field)
{
StringBuilder sb = new StringBuilder();
sb.Append(field);
// Some fields with special characters must be embedded in double quotes
bool embedInQuotes = false;
// Embed in quotes to preserve leading/tralining whitespace
if (sb.Length > 0 &&
(sb[0] == ' ' ||
sb[0] == '\t' ||
sb[sb.Length-1] == ' ' ||
sb[sb.Length-1] == '\t' ))
{
embedInQuotes = true;
}
for (int i = 0; i < sb.Length; i++)
{
// Embed in quotes to preserve: commas, line-breaks etc.
if (sb[i] == DelimiterChar ||
sb[i]=='\r' ||
sb[i]=='\n' ||
sb[i] == '"')
{
embedInQuotes = true;
break;
}
}
// If the field itself has quotes, they must each be represented
// by a pair of consecutive quotes.
sb.Replace("\"", "\"\"");
String rv = sb.ToString();
if (embedInQuotes)
{
rv = "\"" + rv + "\"";
}
return rv;
}
Might not be world's most efficient code, but it has been tested. Real world sucks compared to quick sample code :)
Why not use one of the open source CSV libraries out there?
I know it sounds like overkill for something that appears so simple, but as you can tell by the comments and code snippets, there's more than meets the eye. In addition to handling full CSV compliance, you'll eventually want to handle both reading and writing CSVs... and you may want file manipulation.
I've used Open CSV on one of my projects before (but there are plenty of others to choose from). It certainly made my life easier. ;)
Don't forget our old friend "for". It's not as nice-looking as foreach but it has the advantage of being able to start at the second element.
public string ReturnAsCSV(ContactList contactList)
{
if (contactList == null || contactList.Count == 0)
return string.Empty;
StringBuilder sb = new StringBuilder(contactList[0].Name);
for (int i = 1; i < contactList.Count; i++)
{
sb.Append(",");
sb.Append(contactList[i].Name);
}
return sb.ToString();
}
You could also wrap the second Append in an "if" that tests whether the Name property contains a double-quote or a comma, and if so, escape them appropriately.
You could instead add the comma as the first thing inside your foreach.
if (sb.Length > 0) sb.Append(",");
You could also make an array of c.Name data and use String.Join method to create your line.
public string ReturnAsCSV(ContactList contactList)
{
List<String> tmpList = new List<string>();
foreach (Contact c in contactList)
{
tmpList.Add(c.Name);
}
return String.Join(",", tmpList.ToArray());
}
This might not be as performant as the StringBuilder approach, but it definitely looks cleaner.
Also, you might want to consider using .CurrentCulture.TextInfo.ListSeparator instead of a hard-coded comma -- If your output is going to be imported into other applications, you might have problems with it. ListSeparator may be different across different cultures, and MS Excel at the very least, honors this setting. So:
return String.Join(
System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator,
tmpList.ToArray());
I like the idea of adding the comma by checking if the container is empty, but doesn't that mean more processing as it needs to check the length of the string on each occurrence?
You're prematurely optimizing, the performance hit would be negligible.
Just a thought, but remember to handle comma's and quotation marks (") in the field values, otherwise your CSV file may break the consumers reader.
I wrote a small class for this in case someone else finds it useful...
public class clsCSVBuilder
{
protected int _CurrentIndex = -1;
protected List<string> _Headers = new List<string>();
protected List<List<string>> _Records = new List<List<string>>();
protected const string SEPERATOR = ",";
public clsCSVBuilder() { }
public void CreateRow()
{
_Records.Add(new List<string>());
_CurrentIndex++;
}
protected string _EscapeString(string str)
{
return string.Format("\"{0}\"", str.Replace("\"", "\"\"")
.Replace("\r\n", " ")
.Replace("\n", " ")
.Replace("\r", " "));
}
protected void _AddRawString(string item)
{
_Records[_CurrentIndex].Add(item);
}
public void AddHeader(string name)
{
_Headers.Add(_EscapeString(name));
}
public void AddRowItem(string item)
{
_AddRawString(_EscapeString(item));
}
public void AddRowItem(int item)
{
_AddRawString(item.ToString());
}
public void AddRowItem(double item)
{
_AddRawString(item.ToString());
}
public void AddRowItem(DateTime date)
{
AddRowItem(date.ToShortDateString());
}
public static string GenerateTempCSVPath()
{
return Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString().ToLower().Replace("-", "") + ".csv");
}
protected string _GenerateCSV()
{
StringBuilder sb = new StringBuilder();
if (_Headers.Count > 0)
{
sb.AppendLine(string.Join(SEPERATOR, _Headers.ToArray()));
}
foreach (List<string> row in _Records)
{
sb.AppendLine(string.Join(SEPERATOR, row.ToArray()));
}
return sb.ToString();
}
public void SaveAs(string path)
{
using (StreamWriter sw = new StreamWriter(path))
{
sw.Write(_GenerateCSV());
}
}
}
I've used this method before. The Length property of StringBuilder is NOT readonly so subtracting it by one means truncate the last character. But you have to make sure your length is not zero to start with (which would happen if your list is empty) because setting the length to less than zero is an error.
public string ReturnAsCSV(ContactList contactList)
{
StringBuilder sb = new StringBuilder();
foreach (Contact c in contactList)
{
sb.Append(c.Name + ",");
}
if (sb.Length > 0)
sb.Length -= 1;
return sb.ToString();
}
I use CSVHelper - it's a great open-source library that lets you generate compliant CSV streams one element at a time or custom-map your classes:
public string ReturnAsCSV(ContactList contactList)
{
StringBuilder sb = new StringBuilder();
using (StringWriter stringWriter = new StringWriter(sb))
{
using (var csvWriter = new CsvHelper.CsvWriter(stringWriter))
{
csvWriter.Configuration.HasHeaderRecord = false;
foreach (Contact c in contactList)
{
csvWriter.WriteField(c.Name);
}
}
}
return sb.ToString();
}
or if you map then something like this: csvWriter.WriteRecords<ContactList>(contactList);
How about some trimming?
public string ReturnAsCSV(ContactList contactList)
{
StringBuilder sb = new StringBuilder();
foreach (Contact c in contactList)
{
sb.Append(c.Name + ",");
}
return sb.ToString().Trim(',');
}
How about tracking whether you are on the first item, and only add a comma before the item if it is not the first one.
public string ReturnAsCSV(ContactList contactList)
{
StringBuilder sb = new StringBuilder();
bool isFirst = true;
foreach (Contact c in contactList) {
if (!isFirst) {
// Only add comma before item if it is not the first item
sb.Append(",");
} else {
isFirst = false;
}
sb.Append(c.Name);
}
return sb.ToString();
}