StringBuilder vs string constructor - the character - c#

I just saw a code like
StringBuilder result = new StringBuilder();
for (int i = 0; i < n; i++)
{
result.Append("?");
}
return result.ToString();
I know that concatenating with StringBuilder is considered faster (and does not create new instance of a string on every append). But is there any reason we would not prefer to write
return new string('?', n)
instead?

But is there any reason we would not prefer to write return new string("?", n) instead
The only reason I can think of is unfamiliarity of the developer with the existence of this string constructor. But for people familiar with it, no, there is no reason to not use it.
Also you probably meant:
return new string('?', n)

The main reason not to use new string("?", n) is that no such constructor exists and it won't compile. However, there is absolutely no reason not to use new string('?', n), and I fully encourage you to do so.

Related

Remove part of a string from List<string>

I have a List<string>, the string part representing filenames that I need to filter out: anything that comes before the character '&' included must be erased.
List<string> zippedTransactions = new List<string>();
zippedTransactions.Add("33396&20151007112154000549659S03333396SUMMARIES.PDF");
zippedTransactions.Add("33395&20151007112400000549659S03333395SUMMARIES.PDF");
zippedTransactions.Add("33397&20151007112555000549659S03333397SUMMARIES.PDF");
// desired output:
// "20151007112154000549659S03333396SUMMARIES.PDF";
// "20151007112400000549659S03333395SUMMARIES.PDF";
// "20151007112555000549659S03333397SUMMARIES.PDF"
NOTE: I don't want to give it the classic iterative-style look, since C# provides for plentiful of functional interfaces to interact with this sort of data structure, I want to start using it.
Here is one Linq approach with RegEx
Transactions = Transactions.Select(x => Regex.Replace(x, ".*&", string.Empty)).ToList();
That's more fault tolerant compared to Split('&')[1] in case there is no & in your filename
Try this
for (int i = 0; i < zippedTransactions.Count; i++)
{
zippedTransactions[i] = zippedTransactions[i].Split('&')[1];
}
If you happen to have visual studio, and a version that supports C# Interactive, I suggest you try this.
> zippedTransactions = new List<string>() {
"33396&20151007112154000549659S03333396SUMMARIES.PDF",
"33395&20151007112400000549659S03333395SUMMARIES.PDF",
"33397&20151007112555000549659S03333397SUMMARIES.PDF"
};
>
> zippedTransactions.Select(dirname => dirname.Split('&')[1])
Enumerable.WhereSelectListIterator<string, string> { "20151007112154000549659S03333396SUMMARIES.PDF", "20151007112400000549659S03333395SUMMARIES.PDF", "20151007112555000549659S03333397SUMMARIES.PDF" }
>
And even if you don't, you can get an idea of what's happening just by looking at the code.
The WhereSelectListIterator is a data structure holding the logic you intend to execute on the data structure. It is evaluated (read: the loop actually happens) only when you consume it (for example, calling .ToList() at the end).
This code will only take the second element coming after splitting the string on '&', so you might wanna generalize it or tune it for your requirements.
Use string.Split to split the string at the desired character and retrieve the portion that you want:
foreach (var item in zippedTransactions)
{
string[] result = item.Split('&');
Console.WriteLine(result[1]);
}
You can use the string.IndexOf function to find the location of a character in the string and then use string.Remove to remove the characters up to that point:
for(int i =0; i < zippedTransactions.Count; i++)
{
int count = zippedTransactions[i].IndexOf("&") + 1;
zippedTransactions[i] = zippedTransactions[i].Remove(0, count);
}
following code will help you
for (int i = 0; i < zippedTransactions.Count; i++)
{
string[] result = zippedTransactions[i].Split('&');
zippedTransactions[i] = result[result.Length-1];
}

Best way to remove the last character from a string built with stringbuilder

I have the following
data.AppendFormat("{0},",dataToAppend);
The problem with this is that I am using it in a loop and there will be a trailing comma. What is the best way to remove the trailing comma?
Do I have to change data to a string and then substring it?
The simplest and most efficient way is to perform this command:
data.Length--;
by doing this you move the pointer (i.e. last index) back one character but you don't change the mutability of the object. In fact, clearing a StringBuilder is best done with Length as well (but do actually use the Clear() method for clarity instead because that's what its implementation looks like):
data.Length = 0;
again, because it doesn't change the allocation table. Think of it like saying, I don't want to recognize these bytes anymore. Now, even when calling ToString(), it won't recognize anything past its Length, well, it can't. It's a mutable object that allocates more space than what you provide it, it's simply built this way.
Just use
string.Join(",", yourCollection)
This way you don't need the StringBuilder and the loop.
Long addition about async case. As of 2019, it's not a rare setup when the data are coming asynchronously.
In case your data are in async collection, there is no string.Join overload taking IAsyncEnumerable<T>. But it's easy to create one manually, hacking the code from string.Join:
public static class StringEx
{
public static async Task<string> JoinAsync<T>(string separator, IAsyncEnumerable<T> seq)
{
if (seq == null)
throw new ArgumentNullException(nameof(seq));
await using (var en = seq.GetAsyncEnumerator())
{
if (!await en.MoveNextAsync())
return string.Empty;
string firstString = en.Current?.ToString();
if (!await en.MoveNextAsync())
return firstString ?? string.Empty;
// Null separator and values are handled by the StringBuilder
var sb = new StringBuilder(256);
sb.Append(firstString);
do
{
var currentValue = en.Current;
sb.Append(separator);
if (currentValue != null)
sb.Append(currentValue);
}
while (await en.MoveNextAsync());
return sb.ToString();
}
}
}
If the data are coming asynchronously but the interface IAsyncEnumerable<T> is not supported (like the mentioned in comments SqlDataReader), it's relatively easy to wrap the data into an IAsyncEnumerable<T>:
async IAsyncEnumerable<(object first, object second, object product)> ExtractData(
SqlDataReader reader)
{
while (await reader.ReadAsync())
yield return (reader[0], reader[1], reader[2]);
}
and use it:
Task<string> Stringify(SqlDataReader reader) =>
StringEx.JoinAsync(
", ",
ExtractData(reader).Select(x => $"{x.first} * {x.second} = {x.product}"));
In order to use Select, you'll need to use nuget package System.Interactive.Async. Here you can find a compilable example.
How about this..
string str = "The quick brown fox jumps over the lazy dog,";
StringBuilder sb = new StringBuilder(str);
sb.Remove(str.Length - 1, 1);
Use the following after the loop.
.TrimEnd(',')
or simply change to
string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)
I prefer manipulating the length of the stringbuilder:
data.Length = data.Length - 1;
I recommend, you change your loop algorithm:
Add the comma not AFTER the item, but BEFORE
Use a boolean variable, that starts with false, do suppress the first comma
Set this boolean variable to true after testing it
You should use the string.Join method to turn a collection of items into a comma delimited string. It will ensure that there is no leading or trailing comma, as well as ensure the string is constructed efficiently (without unnecessary intermediate strings).
The most simple way would be to use the Join() method:
public static void Trail()
{
var list = new List<string> { "lala", "lulu", "lele" };
var data = string.Join(",", list);
}
If you really need the StringBuilder, trim the end comma after the loop:
data.ToString().TrimEnd(',');
Yes, convert it to a string once the loop is done:
String str = data.ToString().TrimEnd(',');
You have two options. First one is very easy use Remove method it is quite effective. Second way is to use ToString with start index and end index (MSDN documentation)
Similar SO question here.
I liked the using a StringBuilder extension method.
RemoveLast Method
Gotcha!!
Most of the answers on this thread won't work if you use AppendLine like below:
var builder = new StringBuilder();
builder.AppendLine("One,");
builder.Length--; // Won't work
Console.Write(builder.ToString());
builder = new StringBuilder();
builder.AppendLine("One,");
builder.Length += -1; // Won't work
Console.Write(builder.ToString());
builder = new StringBuilder();
builder.AppendLine("One,");
Console.Write(builder.TrimEnd(',')); // Won't work
Fiddle Me
WHY??? #(&**(&#!!
The issue is simple but took me a while to figure it out: Because there are 2 more invisible characters at the end CR and LF (Carriage Return and Line Feed). Therefore, you need to take away 3 last characters:
var builder = new StringBuilder();
builder.AppendLine("One,");
builder.Length -= 3; // This will work
Console.WriteLine(builder.ToString());
In Conclusion
Use Length-- or Length -= 1 if the last method you called was Append. Use Length =- 3 if you the last method you called AppendLine.
Simply shortens the stringbuilder length by 1;
StringBuilder sb = new StringBuilder();
sb.Length--;
i know this is not the effective way as it translates to sb = sb-1;
Alternative Effective solution
sb.Remove(starting_index, how_many_character_to_delete);
for our case it would be
sb.Remove(sb.length-1,1)

Linq method for creating a sequence of separate objects?

I must be reinventing the wheel here - but I've searched and I can't find anything quite the same...
Here's my code for creating a sequence of zero or more objects that have a default constructor:
public static IEnumerable<T> CreateSequence<T>(int n) where T: new()
{
for (int i = 0; i < n; ++i)
{
yield return new T();
}
}
My question is quite simple: Is there a Linq equivalent of this I should be using?
Try this:
Enumerable.Range(1,count).Select(_ => new T());
Enumerable.Range will give you the current number from the specified range as parameter, but you can simply ignore that (named as _ in the example).
Yes there is: var items = Enumerable.Repeat(new YourClass(), 10);

What string concatenation method to use for N number of iterations?

If I want to concatenate a string N number of times, which method should i prefer?
Take this code as an example:
public static string Repeat(this string instance, int times)
{
var result = string.Empty;
for (int i = 0; i < times; i++)
result += instance;
return result;
}
This method may be invoked with "times" set to 5, or 5000. What method should I prefer to use?
string.Join? Stringbuilder? Just standard string.Concat?
A similar function is going to be implemented in a commercial library so I really need the "optimal" way to do this.
public static string Repeat(this string instance, int times)
{
if (times == 1 || string.IsNullOrEmpty(instance)) return instance;
if (times == 0) return "";
if (times < 0) throw new ArgumentOutOfRangeException("times");
StringBuilder sb = new StringBuilder(instance.Length * times);
for (int i = 0; i < times; i++)
sb.Append(instance);
return sb.ToString();
}
Stringbuilder ofcourse. It is meant for fast string join operations, because it won't create new object each time you want to join a string.
For details see here.
StringBuilder.
"result += result;"
creates a new string each and every time you do the assignment, and assign that new string to your variable, since strings are immutable.
Go with StringBuilder, definitely.
Never make an assumption that one method is faster than another -- you must alway measure the performance of both and then decide.
Surprisingly, for smaller numbers of iterations, just a standard string concatenation (result += string) is often faster than using a string builder.
If you know that the number of iterations will always be the same (e.g. it will always be 50 iterations), then I would suggest that you make some performance measurements using different methods.
If you really want to get clever, make performance measurements against number of iterations and you can find the 'crossover point' where one method is faster than another and hard-code that threshold into the method:
if(iterations < 30)
{
CopyWithConcatenation();
}
else
{
CopyWithStringBuilder();
}
The exact performance crossover point will depend on the specific detail of your code and you'll never be able to find out what they are without making performance measurements.
To complicate things a little more, StringBuilder has 'neater' memory management that string concatenation (which creates more temporary instances) so this might also have an impact on overall performance outside of your string loop (like the next time the Garbage Collector runs).
Let us know how you got on.

How Bad is this Code?

Ok, I am an amateur programmer and just wrote this. It gets the job done, but I am wondering how bad it is and what kind of improvements could be made.
[Please note that this is a Chalk extension for Graffiti CMS.]
public string PostsAsSlides(PostCollection posts, int PostsPerSlide)
{
StringBuilder sb = new StringBuilder();
decimal slides = Math.Round((decimal)posts.Count / (decimal)PostsPerSlide, 3);
int NumberOfSlides = Convert.ToInt32(Math.Ceiling(slides));
for (int i = 0; i < NumberOfSlides; i++ )
{
int PostCount = 0;
sb.Append("<div class=\"slide\">\n");
foreach (Post post in posts.Skip<Post>(i * PostsPerSlide))
{
PostCount += 1;
string CssClass = "slide-block";
if (PostCount == 1)
CssClass += " first";
else if (PostCount == PostsPerSlide)
CssClass += " last";
sb.Append(string.Format("<div class=\"{0}\">\n", CssClass));
sb.Append(string.Format("<img src=\"{2}\" alt=\"{3}\" />\n", post.Custom("Large Image"), post.MetaDescription, post.ImageUrl, post.Title));
sb.Append(string.Format("<a class=\"button-launch-website\" href=\"{0}\" target=\"_blank\">Launch Website</a>\n", post.Custom("Website Url")));
sb.Append("</div><!--.slide-block-->\n");
if (PostCount == PostsPerSlide)
break;
}
sb.Append("</div><!--.slide-->\n");
}
return sb.ToString();
}
Use an HtmlTextWriter instead of a StringBuilder to write HTML:
StringBuilder sb = new StringBuilder();
using(HtmlTextWriter writer = new HtmlTextWriter(new StringWriter(sb)))
{
writer.WriteBeginTag("div");
writer.WriteAttribute("class", "slide");
//...
}
return sb.ToString();
We don't want to use an unstructured writer to write structured data.
Break the body of your inner loop into a separate routine:
foreach(...)
{
WritePost(post, writer);
}
//snip
private void WritePost(Post post, HtmlTextWriter writer)
{
//write single post
}
This makes it testable and more easily modifiable.
Use a data structure for managing things like CSS classes.
Instead of appending extra class names with a space and hoping everything lines up right at the end, keep a List<string> of class names to add and remove as necessary, and then call:
List<string> cssClasses = new List<string>();
//snip
string.Join(" ", cssClasses.ToArray());
Instead of this,
foreach (Post post in posts.Skip<Post>(i * PostsPerSlide))
{
// ...
if (PostCount == PostsPerSlide)
break;
}
I'd move the exit condition into the loop like so: (and eliminate unnecessary generic parameter while you're at it)
foreach (Post post in posts.Skip(i * PostsPerSlide).Take(PostsPerSlide))
{
// ...
}
As a matter of fact, your two nested loops can probably be handled in one single loop.
Also, I'd prefer to use single quotes for the html attributes, since those are legal and don't require escaping.
It's not great but I've seen much much worse.
Assuming this is ASP.Net, is there a reason you're using this approach instead of a repeater?
EDIT:
If a repeater isn't possible in this instance I would recommend using the .Net HTML classes to generate the HTML instead of using text. It's a little easier to read/adjust later on.
I've seen much worse, but you could improve it quite a bit.
Instead of StringBuilder.Append() with a string.Format() inside, use StringBuilder.AppendFormat()
Add some unit tests around it to ensure that it's producing the output you want, then refactor the code inside to be better. The tests will ensure that you haven't broken anything.
My first thought is that this would be very difficult to unit test.
I would suggest having the second for loop be a separate function, so you would have something like:
for (int i = 0; i < NumberOfSlides; i++ )
{
int PostCount = 0;
sb.Append("<div class=\"slide\">\n");
LoopPosts(posts, i);
...
and LoopPosts:
private void LoopPosts(PostCollection posts, int i) {
...
}
If you get into a habit of doing loops like this then when you need to do a unit test it will be easier to test the inner loop separate from the outer loop.
I'd say that it looks good enough, there is no serious problems with the code, and it would work well. It doesn't mean that it can't be improved, though. :)
Here are a few tips:
For general floating point operations you should use double rather than Decimal. However, in this case you don't need any floating point arithmetic at all, you can do this with just integers:
int numberOfSlides = (posts.Count + PostsPerSlide - 1) / PostsPerSlide;
But, if you just use a single loop over all items instead of a loop in a loop, you don't need the slide count at all.
The convention for local variables is camel case (postCoint) rather than pascal case (PostCount). Try to make variable names meaningdful rather than obscure abbreviations like "sb". If the scope of a variable is so small that you don't really need a meaningful name for it, just go for the simplest possible and just a single letter instead of an abbreviation.
Instead of concatenating the strings "slide block" and " first" you can assign literal strings directly. That way you replace a call to String.Concat with a simple assignment. (This borders on premature optimisation, but on the other hand the string concatenation takes about 50 times longer.)
You can use .AppendFormat(...) instead of .Append(String.Format(...)) on a StringBuilder. I would just stick to Append in this case, as there isn't really anything that needs formatting, you are just concatenating strings.
So, I would write the method like this:
public string PostsAsSlides(PostCollection posts, int postsPerSlide) {
StringBuilder builder = new StringBuilder();
int postCount = 0;
foreach (Post post in posts) {
postCount++;
string cssClass;
if (postCount == 1) {
builder.Append("<div class=\"slide\">\n");
cssClass = "slide-block first";
} else if (postCount == postsPerSlide) {
cssClass = "slide-block last";
postCount = 0;
} else {
cssClass = "slide-block";
}
builder.Append("<div class=\"").Append(cssClass).Append("\">\n")
.Append("<a href=\"").Append(post.Custom("Large Image"))
.Append("\" rel=\"prettyPhoto[gallery]\" title=\"")
.Append(post.MetaDescription).Append("\"><img src=\"")
.Append(post.ImageUrl).Append("\" alt=\"").Append(post.Title)
.Append("\" /></a>\n")
.Append("<a class=\"button-launch-website\" href=\"")
.Append(post.Custom("Website Url"))
.Append("\" target=\"_blank\">Launch Website</a>\n")
.Append("</div><!--.slide-block-->\n");
if (postCount == 0) {
builder.Append("</div><!--.slide-->\n");
}
}
return builder.ToString();
}
It would help if the definition for posts existed, but in general, I would say that generating HTML in code behind is not the way to go in Asp.Net.
Since this is tagged as .Net specifically...
For displaying a list of items in a collection, you'd be better off looking at one of the data controls (Repeater, Data List, etc) and learning how to use those properly.
http://quickstarts.asp.net/QuickStartv20/aspnet/doc/ctrlref/data/default.aspx
Another piece of tightening you could do: replace the sb.Append(string.Format(...)) calls with sb.AppendFormat(...).

Categories

Resources