I already knew that in C#, string are reference type that are handled mostly like a value type, but I recently discover this:
http://net-informations.com/faq/general/immutable.htm
I'm trying to "prove" that string are immutable by printing the reference to a string variable anytime a concatenation occurs:
string s = string.Empty;
for (int i = 0; i < 10; i++)
{
s += " " + i;
// Console.WriteLine(&s);
}
As you can guess, the code doesn't compile if the line is uncommented (Compiler Error CS0208: Cannot take the address of, get the size of, or declare a pointer to a managed type ('type')).
I also tried the GetHashCode(), but apparently for string it returns the same value based on the... String value itself (ie. it will always return the same result for "0" for instance).
Is there a way to do that? It won't be for a coding purpose, just for an understanding purpose... If not, how can you know that it will "really" create n instances of a string at each concatenation? Appart from "knowing it"...
EDIT: probably not a duplicate of C# memory address and variable, because I want to know how to get the address of a reference type, and apparently that thread deals only with value type.
If you want to check that a new instance was created, just compare it to the previous instance:
string s = string.Empty;
for (int i = 0; i < 10; i++)
{
var saved = s;
s += " " + i;
if (ReferenceEquals(saved, s))
Console.WriteLine("oops, no new instance created?");
}
You'll see that this won't print anything.
You can use the ObjectIDGenerator to generate ids for the strings and see if the ids are the same.
var string1 = "Jerry";
ObjectIDGenerator generator = new ObjectIDGenerator();
bool string1FirstTime;
long string1Id = generator.GetId(string1, out string1FirstTime);
bool string2FirstTime;
var string2 = string1 + " Seinfeld";
long string2Id = generator.GetId(string2, out string2FirstTime);
Below will return false because they are not the same.
var same = string1Id == string2Id;
You can copy the string to another variable, and compare their references and value before and after modification:
string s = string.Empty;
for (int i = 0; i < 10; i++)
{
var copy = s;
// true
Console.WriteLine("both are the same reference: {0}", Object.ReferenceEquals(s, copy));
// true
Console.WriteLine("both are the same value: {0}", s.Equals(copy));
s += " " + i;
// false
Console.WriteLine("both are the same reference: {0}", Object.ReferenceEquals(s, copy));
// false
Console.WriteLine("both are the same value: {0}", s.Equals(copy));
}
Related
This question already has answers here:
C# parsing a text file and storing the values in an array
(3 answers)
Closed 5 years ago.
I am trying to store values in an array from reading from a file. I have the reading from a file part but I can't get it to store in an array because it gives me an error "Value cannot be null" because after the loop the value of my variable becomes null and the array cannot be null. Here's what I have. And I realize that the for loop probably isn't in the correct spot so any help with where to put it would be great.
Program p = new Program();
int MAX = 50;
int[] grades = new int[MAX];
string environment = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal) + "\\";
string path = environment + "grades.txt";
StreamReader myFile = new StreamReader(path);
string input;
int count = 0;
do
{
input = myFile.ReadLine();
if (input != null)
{
WriteLine(input);
count++;
}
} while (input != null);
for (int i = 0; i < count; i++)
{
grades[i] = int.Parse(input);
}
You start the for loop just after exiting from the while loop. And the condition to exit from the while loop is true when input is null. Of course this is not well accepted by Int.Parse.
Instead you can use a single loop, taking in consideration that you don't want to loop more than 50 times otherwise you exceed the array dimensions
int count = 0;
while((input = myFile.ReadLine()) != null && count < 50)
{
WriteLine(input);
grades[count] = int.Parse(input);
count++;
}
However you can have a more flexible way to handle your input if you use a List<int> instead of an array of integers. In this way you don't have to check for the number of lines present in your file
List<int> grades = new List<int>();
while((input = myFile.ReadLine()) != null)
grades.Add(int.Parse(input));
if we want to get really condensed
var grades = File.ReadAllLines(path).Select(l=>Int.Parse(l)).ToArray();
Utilize the Path.Combine() to help you in concatenating paths.
string environment = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
String fullPath = Path.Combine(environment, "grades.txt");
int[] grades = File.ReadAllLines(fullPath).Select(p => int.Parse(p)).ToArray<int>();
Console.WriteLine(grades);
Refer to https://www.dotnetperls.com/file-readalllines on how to use File.ReadAllLines() its very handy.
I'm using LINQ here, which sometimes simplifies things. Even though it looks a bit intimidating now. We read all lines, the result of that is then parsed by selecting each one and converting it to an integer then outputting an array of integers and saving that to grades.
Program p = new Program();
int MAX = 50;
int[] grades = new int[MAX];
string environment = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal) + "\\";
string path = environment + "grades.txt";
using (StreamReader myFile = new StreamReader(path))
{
string input;
int count = 0;
while((!myFile.EndOfStream) && (count < MAX))
{
input = myFile.ReadLine();
if (!String.IsNullOrWhiteSpace(input))
{
WriteLine(input);
grades[count] = int.Parse(input);
count++;
}
}
}
You should definitely use the "using" pattern around your stream object. Got rid of the for-loop for you while maintaining mostly your code and style. Your issue was that you weren't using the input value before moving on to the next line. You only ever had the last value in your original code.
Let's say I have a string like this one, left part is a word, right part is a collection of indices (single or range) used to reference furigana (phonetics) for kanjis in my word:
string myString = "子で子にならぬ時鳥,0:こ;2:こ;7-8:ほととぎす"
The pattern in detail:
word,<startIndex>(-<endIndex>):<furigana>
What would be the best way to achieve something like this (with a space in front of the kanji to mark which part is linked to the [furigana]):
子[こ]で 子[こ]にならぬ 時鳥[ほととぎす]
Edit: (thanks for your comments guys)
Here is what I wrote so far:
static void Main(string[] args)
{
string myString = "ABCDEF,1:test;3:test2";
//Split Kanjis / Indices
string[] tokens = myString.Split(',');
//Extract furigana indices
string[] indices = tokens[1].Split(';');
//Dictionnary to store furigana indices
Dictionary<string, string> furiganaIndices = new Dictionary<string, string>();
//Collect
foreach (string index in indices)
{
string[] splitIndex = index.Split(':');
furiganaIndices.Add(splitIndex[0], splitIndex[1]);
}
//Processing
string result = tokens[0] + ",";
for (int i = 0; i < tokens[0].Length; i++)
{
string currentIndex = i.ToString();
if (furiganaIndices.ContainsKey(currentIndex)) //add [furigana]
{
string currentFurigana = furiganaIndices[currentIndex].ToString();
result = result + " " + tokens[0].ElementAt(i) + string.Format("[{0}]", currentFurigana);
}
else //nothing to add
{
result = result + tokens[0].ElementAt(i);
}
}
File.AppendAllText(#"D:\test.txt", result + Environment.NewLine);
}
Result:
ABCDEF,A B[test]C D[test2]EF
I struggle to find a way to process ranged indices:
string myString = "ABCDEF,1:test;2-3:test2";
Result : ABCDEF,A B[test] CD[test2]EF
I don't have anything against manually manipulating strings per se. But given that you seem to have a regular pattern describing the inputs, it seems to me that a solution that uses regex would be more maintainable and readable. So with that in mind, here's an example program that takes that approach:
class Program
{
private const string _kinvalidFormatException = "Invalid format for edit specification";
private static readonly Regex
regex1 = new Regex(#"(?<word>[^,]+),(?<edit>(?:\d+)(?:-(?:\d+))?:(?:[^;]+);?)+", RegexOptions.Compiled),
regex2 = new Regex(#"(?<start>\d+)(?:-(?<end>\d+))?:(?<furigana>[^;]+);?", RegexOptions.Compiled);
static void Main(string[] args)
{
string myString = "子で子にならぬ時鳥,0:こ;2:こ;7-8:ほととぎす";
string result = EditString(myString);
}
private static string EditString(string myString)
{
Match editsMatch = regex1.Match(myString);
if (!editsMatch.Success)
{
throw new ArgumentException(_kinvalidFormatException);
}
int ichCur = 0;
string input = editsMatch.Groups["word"].Value;
StringBuilder text = new StringBuilder();
foreach (Capture capture in editsMatch.Groups["edit"].Captures)
{
Match oneEditMatch = regex2.Match(capture.Value);
if (!oneEditMatch.Success)
{
throw new ArgumentException(_kinvalidFormatException);
}
int start, end;
if (!int.TryParse(oneEditMatch.Groups["start"].Value, out start))
{
throw new ArgumentException(_kinvalidFormatException);
}
Group endGroup = oneEditMatch.Groups["end"];
if (endGroup.Success)
{
if (!int.TryParse(endGroup.Value, out end))
{
throw new ArgumentException(_kinvalidFormatException);
}
}
else
{
end = start;
}
text.Append(input.Substring(ichCur, start - ichCur));
if (text.Length > 0)
{
text.Append(' ');
}
ichCur = end + 1;
text.Append(input.Substring(start, ichCur - start));
text.Append(string.Format("[{0}]", oneEditMatch.Groups["furigana"]));
}
if (ichCur < input.Length)
{
text.Append(input.Substring(ichCur));
}
return text.ToString();
}
}
Notes:
This implementation assumes that the edit specifications will be listed in order and won't overlap. It makes no attempt to validate that part of the input; depending on where you are getting your input from you may want to add that. If it's valid for the specifications to be listed out of order, you can also extend the above to first store the edits in a list and sort the list by the start index before actually editing the string. (In similar fashion to the way the other proposed answer works; though, why they are using a dictionary instead of a simple list to store the individual edits, I have no idea…that seems arbitrarily complicated to me.)
I included basic input validation, throwing exceptions where failures occur in the pattern matching. A more user-friendly implementation would add more specific information to each exception, describing what part of the input actually was invalid.
The Regex class actually has a Replace() method, which allows for complete customization. The above could have been implemented that way, using Replace() and a MatchEvaluator to provide the replacement text, instead of just appending text to a StringBuilder. Which way to do it is mostly a matter of preference, though the MatchEvaluator might be preferred if you have a need for more flexible implementation options (i.e. if the exact format of the result can vary).
If you do choose to use the other proposed answer, I strongly recommend you use StringBuilder instead of simply concatenating onto the results variable. For short strings it won't matter much, but you should get into the habit of always using StringBuilder when you have a loop that is incrementally adding onto a string value, because for long string the performance implications of using concatenation can be very negative.
This should do it (and even handle ranged indices), based on the formatting of the input string you have-
using System;
using System.Collections.Generic;
public class stringParser
{
private struct IndexElements
{
public int start;
public int end;
public string value;
}
public static void Main()
{
//input string
string myString = "子で子にならぬ時鳥,0:こ;2:こ;7-8:ほととぎす";
int wordIndexSplit = myString.IndexOf(',');
string word = myString.Substring(0,wordIndexSplit);
string indices = myString.Substring(wordIndexSplit + 1);
string[] eachIndex = indices.Split(';');
Dictionary<int,IndexElements> index = new Dictionary<int,IndexElements>();
string[] elements;
IndexElements e;
int dash;
int n = 0;
int last = -1;
string results = "";
foreach (string s in eachIndex)
{
e = new IndexElements();
elements = s.Split(':');
if (elements[0].Contains("-"))
{
dash = elements[0].IndexOf('-');
e.start = int.Parse(elements[0].Substring(0,dash));
e.end = int.Parse(elements[0].Substring(dash + 1));
}
else
{
e.start = int.Parse(elements[0]);
e.end = e.start;
}
e.value = elements[1];
index.Add(n,e);
n++;
}
//this is the part that takes the "setup" from the parts above and forms the result string
//loop through each of the "indices" parsed above
for (int i = 0; i < index.Count; i++)
{
//if this is the first iteration through the loop, and the first "index" does not start
//at position 0, add the beginning characters before its start
if (last == -1 && index[i].start > 0)
{
results += word.Substring(0,index[i].start);
}
//if this is not the first iteration through the loop, and the previous iteration did
//not stop at the position directly before the start of the current iteration, add
//the intermediary chracters
else if (last != -1 && last + 1 != index[i].start)
{
results += word.Substring(last + 1,index[i].start - (last + 1));
}
//add the space before the "index" match, the actual match, and then the formatted "index"
results += " " + word.Substring(index[i].start,(index[i].end - index[i].start) + 1)
+ "[" + index[i].value + "]";
//remember the position of the ending for the next iteration
last = index[i].end;
}
//if the last "index" did not stop at the end of the input string, add the remaining characters
if (index[index.Keys.Count - 1].end + 1 < word.Length)
{
results += word.Substring(index[index.Keys.Count-1].end + 1);
}
//trimming spaces that may be left behind
results = results.Trim();
Console.WriteLine("INPUT - " + myString);
Console.WriteLine("OUTPUT - " + results);
Console.Read();
}
}
input - 子で子にならぬ時鳥,0:こ;2:こ;7-8:ほととぎす
output - 子[こ]で 子[こ]にならぬ 時鳥[ほととぎす]
Note that this should also work with characters the English alphabet if you wanted to use English instead-
input - iliketocodeverymuch,2:A;4-6:B;9-12:CDEFG
output - il i[A]k eto[B]co deve[CDEFG]rymuch
This program throws ArrayIndexOutOfBoundException.
string name = "Naveen";
int c = 0;
while( name[ c ] != '\0' ) {
c++;
}
Console.WriteLine("Length of string " + name + " is: " + c);
Why is it so?
If strings are not null-terminated. How strings are getting handled in C#?
How can I get the length without using string.Length property?
I'm confused here.!
C# does not use NUL terminated strings as C and C++ does. You must use the Length property of the string.
Console.WriteLine("Length of string " + name + " is: " + name.Length.ToString());
or by using formatters
Console.WriteLine("Length of string '{0}' is {1}.", name, name.Length);
public static void Main()
{
unsafe
{
var s = "Naveen";
fixed (char* cp = s)
{
for (int i = 0; cp[i] != '\0'; i++)
{
Console.Write(cp[i]);
}
}
}
}
// prints Naveen
In C/C++ string is stored in is a char array AFAIR without intelligence and behaviour. Therefore, to indicate that such array ends somewhere, one must have added \0 at the end.
On the other hand, in C#, string is a container (a class with properties and methods); as a side note you can assign null to its instantiated object. You don't need to add anything to it to indicate where it ends. The container controlls everything for you. As such, it also has iterator (or enumerator in C# i think). That means you can use foreach and LINQ expressions to iterate over it.
Having said that, you could use a simple counter in a code similar to this to get a length of a string:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LengthOfString
{
class Program
{
static void Main(string[] args)
{
string s = "abcde\0\0\0";
Console.WriteLine(s);
Console.WriteLine("s.Length = " + s.Length);
Console.WriteLine();
// Here I count the number of characters in s
// using LINQ
int counter = 0;
s.ToList()
.ForEach(ch => {
Console.Write(string.Format("{0} ", (int)ch));
counter++;
});
Console.WriteLine(); Console.WriteLine("LINQ: Length = " + counter);
Console.WriteLine(); Console.WriteLine();
//Or you could just use foreach for this
counter = 0;
foreach (int ch in s)
{
Console.Write(string.Format("{0} ", (int)ch));
counter++;
}
Console.WriteLine(); Console.WriteLine("foreach: Length = " + counter);
Console.WriteLine(); Console.WriteLine(); Console.WriteLine(); Console.WriteLine();
Console.WriteLine("Press ENTER");
Console.ReadKey();
}
}
}
You are trying to access a character at an index which is not available according to name length. You may solve it this way:
string name = "Naveen";
int c = 0;
while (c < name.Length)
{
c++;
}
However there is no need to count the length of a string in c# this
way. You can try simply name.Length
EDIT: based on what #NaveenKumarV provide in comments if you want to check for \0 characters then as others said you may try ToCharArray method. Here is the code:
var result = name.ToCharArray().TakeWhile(i => i != '\0').ToList();
I'm trying to make this loop work so it will keep asking for a number until the user enters 999. However with this version of code, it won't build saying I can't declare num within the loop becuase I'm giving it different meaning in this scope.
The try and catch are used because of my assignment rules for this piece of code.
int num;
while (num != 999)
{
Console.WriteLine("Enter a number between 0 and 99");
string input = Console.ReadLine();
try
{
int num = Convert.ToInt32(input);
Console.WriteLine("This is element number " + num + " : " + randNums[num]);
}
catch
{
Console.WriteLine("Data inputted is not between 0 and 99");
}
}
Console.WriteLine("You chose the secret value, well done!");
The problem is that you're declaring the variable twice with int num. You don't need to redeclare the variable within the loop, just assign it:
int num = 0; // initialized num to 0 here
while (num != 999)
{
Console.WriteLine("Enter a number between 0 and 99");
string input = Console.ReadLine();
try
{
num = Convert.ToInt32(input); // Changed int num to num here
Console.WriteLine("This is element number " + num + " : " + randNums[num]);
}
catch
{
Console.WriteLine("Data inputted is not between 0 and 99");
}
}
Console.WriteLine("You chose the secret value, well done!");
You have int num defined twice in the same scope. Change the name of one of them. The one inside the loop is invalid since you already have one defined.
Alternately you can remove int from the inner one if you want to re-assign the same variable. This way it will overwrite it each time with a new value.
Also when you initialize it the first time, be sure to assign it a value.
Example:
int num = 0;
In addition to other answers, you can do this, for example:
if (someCondition)
{
int num = 23;
}
else
{
int num = 12;
}
But you can't do this:
int num = 12;
if(someCondition)
{
int num = 23;
}
Because all variables has it's own scope, and if you define a variable in outer scope, you can't define a new variable with the same name in the inner-scope.So if you just want to update the value of your variable, you don't need to declare it again, just use a simple assignment.See Compiler Error CS0136 documentation for more details.
You're trying to declare 2 different variables of the same name. Instead of declaring the second one, just use the one you already declared (take off inton the second usage).
...
try
{
num = Convert.ToInt32(input); // <-- Using existing declaration here
Console.WriteLine("This is element number " + num + " : " + randNums[num]);
}
...
You have two int variables with the same name. You need to change the other one since you have an int variable for the input that will accept the number assigned by the user, and another int variable responsible for detecting whether the number inputted is 999 or not.
I'm looking to optimized this piece of code. It will process 15000 - 20000 lines. For now I have 9000 lines and it take 30 sec approx. I know string concatenation is slow but I don't know how to do it another way.
//
// Check if composite primary keys existe in database
//
string strSelect = "SELECT * FROM " + _strTableName + " WHERE ";
for (int i = 0; i < strCompositeKeyField.Length; i++)
{
bool boolKeyProcess = false;
strSelect += _strHeaderLineSplitedArray[(int)arrayListCompositeKeyIndex[i]] + " = ";
DataColumn thisColomn = _dsProcessDataFromFileAndPutInDataSetDataSet.Tables["Repartition"].Columns[_strHeaderLineSplitedArray[(int)arrayListCompositeKeyIndex[i]]];
//_strProcessDataFromFileAndPutInDataSetLog += "Debug: Composite key : " + _strHeaderLineSplitedArray[(int)arrayListCompositeKeyIndex[i]] + " dataType : " + thisColomn.DataType.ToString() + " arrayListCompositeKeyIndex[i] = " + arrayListCompositeKeyIndex[i] + " \n";
// check if field is datetime to make convertion
if (thisColomn.DataType.ToString() == "System.DateTime")
{
DateTime thisDateTime = DateTime.ParseExact(strReadDataLineSplited[(int)arrayListCompositeKeyIndex[i]], _strDateConvertion, null);
strSelect += "'" + thisDateTime.ToString() + "'";
boolKeyProcess = true;
}
// check if field a string to add ''
else if (thisColomn.DataType.ToString() == "System.String")
{
strSelect += "'" + strReadDataLineSplited[(int)arrayListCompositeKeyIndex[i]] + "'";
boolKeyProcess = true;
}
// check if field need hour to second converstion
else
{
for (int j = 0; j < strHourToSecondConverstionField.Length; j++)
{
if (strCompositeKeyField[i] == strHourToSecondConverstionField[j])
{
DateTime thisDateTime = DateTime.ParseExact(strReadDataLineSplited[(int)arrayListCompositeKeyIndex[i]], _strHourConvertion, System.Globalization.CultureInfo.CurrentCulture);
strSelect += thisDateTime.TimeOfDay.TotalSeconds.ToString();
boolKeyProcess = true;
}
}
}
// if not allready process process as normal
if (!boolKeyProcess)
{
strSelect += strReadDataLineSplited[(int)arrayListCompositeKeyIndex[i]];
}
// Add " AND " if not last field
if (i != strCompositeKeyField.Length - 1)
{
strSelect += " AND ";
}
}
//_strProcessDataFromFileAndPutInDataSetLog += "Debug: SELECT = " + strSelect + "\n";
SqlDataAdapter AdapterCheckCompositePrimaryKeys = new SqlDataAdapter(strSelect, _scProcessDataFrinFileAndPutInDataSetSqlConnection);
DataSet DataSetCheckCompositePrimaryKeys = new DataSet();
AdapterCheckCompositePrimaryKeys.Fill(DataSetCheckCompositePrimaryKeys, "PrimaryKey");
You should definitely take a look at StringBuilder - it works wonders for scenarios like this one. In this case, i'd use a mix of AppendFormat and Append. I tend to like AppendFormat to make the strings a bit easier to follow.
//
// Check if composite primary keys existe in database
//
StringBuilder strSelect = "SELECT * FROM " + _strTableName + " WHERE ";
for (int i = 0; i < strCompositeKeyField.Length; i++)
{
bool boolKeyProcess = false;
strSelect.AppendFormat("{0} =",
_strHeaderLineSplitedArray[(int)arrayListCompositeKeyIndex[i]]);
DataColumn thisColomn =
_dsProcessDataFromFileAndPutInDataSetDataSet
.Tables["Repartition"]
.Columns[_strHeaderLineSplitedArray[(int)arrayListCompositeKeyIndex[i]]];
//_strProcessDataFromFileAndPutInDataSetLog += "Debug: Composite key : " + _strHeaderLineSplitedArray[(int)arrayListCompositeKeyIndex[i]] + " dataType : " + thisColomn.DataType.ToString() + " arrayListCompositeKeyIndex[i] = " + arrayListCompositeKeyIndex[i] + " \n";
// check if field is datetime to make convertion
if (thisColomn.DataType.ToString() == "System.DateTime")
{
DateTime thisDateTime =
DateTime.ParseExact(strReadDataLineSplited[(int)arrayListCompositeKeyIndex[i]],
_strDateConvertion, null);
strSelect.AppendFormat("'{0}'", thisDateTime.ToString());
boolKeyProcess = true;
}
// check if field a string to add ''
else if (thisColomn.DataType.ToString() == "System.String")
{
strSelect.AppendFormat("'{0}'",
strReadDataLineSplited[(int)arrayListCompositeKeyIndex[i]]);
boolKeyProcess = true;
}
// check if field need hour to second converstion
else
{
for (int j = 0; j < strHourToSecondConverstionField.Length; j++)
{
if (strCompositeKeyField[i] == strHourToSecondConverstionField[j])
{
DateTime thisDateTime = DateTime.ParseExact(
strReadDataLineSplited[(int)arrayListCompositeKeyIndex[i]],
_strHourConvertion,
System.Globalization.CultureInfo.CurrentCulture);
strSelect.Append(thisDateTime.TimeOfDay.TotalSeconds.ToString());
boolKeyProcess = true;
}
}
}
// if not allready process process as normal
if (!boolKeyProcess)
{
strSelect.Append(strReadDataLineSplited[(int)arrayListCompositeKeyIndex[i]]);
}
// Add " AND " if not last field
if (i != strCompositeKeyField.Length - 1)
{
strSelect.Append(" AND ");
}
}
//_strProcessDataFromFileAndPutInDataSetLog += "Debug: SELECT = " + strSelect + "\n";
SqlDataAdapter AdapterCheckCompositePrimaryKeys = new SqlDataAdapter(strSelect.ToString(), _scProcessDataFrinFileAndPutInDataSetSqlConnection);
DataSet DataSetCheckCompositePrimaryKeys = new DataSet();
AdapterCheckCompositePrimaryKeys.Fill(DataSetCheckCompositePrimaryKeys, "PrimaryKey");
Use a StringBuilder and its Append() method.
Make use of a StringBuilder rather than string concatenation.
Use StringBuilder for your string manipulation like strSelect += ...
instead use stringBuilder.Append("...");
Have u tried the StringBuilder object ?
http://msdn.microsoft.com/en-us/library/system.text.stringbuilder.aspx
To answer your direct question you'll almost certainly benefit from using StringBuilder to build the string up, then ToString() it at the end.
However, if you could give us an overview of the intention (so we don't have to wade through to deduce it) we can probably recommend a better way.
After a quick look, one thing that stands out is that you should be using the StringBuilder class to build up the string, instead of continually concatenating on to your strSelect string variable. Excerpt from the linked MSDN article:
The performance of a concatenation operation for a String or
StringBuilder object depends on how
often a memory allocation occurs. A
String concatenation operation always
allocates memory, whereas a
StringBuilder concatenation operation
only allocates memory if the
StringBuilder object buffer is too
small to accommodate the new data.
Consequently, the String class is
preferable for a concatenation
operation if a fixed number of String
objects are concatenated. In that
case, the individual concatenation
operations might even be combined into
a single operation by the compiler. A
StringBuilder object is preferable for
a concatenation operation if an
arbitrary number of strings are
concatenated; for example, if a loop
concatenates a random number of
strings of user input.
I'm a database guy, so hopefully I don't sound like an idiot here, but can you use the StringBuilder class? I don't know if that requires the .NET framework or not.
You've received a lot of good suggestions to use StringBuilder. To improve your performance more and since you are expecting a large string result, I would recommend as well, that you choose a good initial capacity to reduce the number of times the internal string buffer needs to be expanded.
StringBuilder strSelect = new StringBuilder("SELECT * FROM " + _strTableName + " WHERE ", 8192);
Note that I picked 8192 characters here, but you may want to initialize this to a larger number if you really are adding "9000" lines of data to your condition. Find out your typical size and set it the capacity to just a small amount above that.
The way StringBuilder works is that as you append to the string and reach the current capacity, it must create a new buffer and copy the contents of the old buffer to the new one then append the new characters. To optimize performance, the new buffer will be double the old size. Now, the default initial capacity is either 16 characters or the size of the initializing string. If your resulting string is 5000 characters long, then a StringBuilder created with a default size will have to be expanded 9 times - that requires new memory allocation and copying all the previous characters! If, however, you knew this would happen regularly and created the StringBuilder with the right capacity, there would be no additional allocation or copies.
Normally you should not need to worry about internal operations of an object, but you do sometimes for performance such as this. Since StringBuilder does provide a way for you to specific a recommended initial capacity, take advantage of that. You do not know if the doubly/copy algorithm will change in the future nor the initial default capacity may change, but your specification of an initial capacity will continue to allocate the right sized builder - because this is part of the public contract.