HI WELCOME TO KANSIRIS

Difference Between String And StringBuilder In C#

Leave a Comment

 

Introduction

Difference between string and stringbuilder in C# is one of the frequently asked question in all c# interviews and all of us answer something in common

String is Immutable and StringBuilder is Mutable

This is a one word answer. In this article I would like to dig things in more details

Don’t Ignore Basics

Both String and Stringbuilder represent sequence of characters. When we list out their differences some of us don’t remember basic difference. That is String Class is in the System Namespace While StringBuilder is in System.Text.

Sometimes Mute is Safe

Let us make the statement again “String is Immutable and StringBuilder is Mutable” [Don’t be Confused about which one is mutable and which one is not].

Immutability of string object Means, If any of your operation on the string instance changes it’s value, it will end up in the creation of new instance in a different address location with modified value. Mutability of StringBuilder is just opposite for this.It won’t create new instance when their content changes as string does.Instead it makes the new changes in the same instance.

Mutability and Immutability of these two can be understood from following C# Program.

//for String Class
using System;
//for StringBuilder Class
using System.Text;

class Program
{
  static void Main(string[] args)
  {
    String str = "My first string was ";
    str += "Hello World";
    //Now str="My first string was Hello World"
    StringBuilder sbr = new StringBuilder("My Favourite Programming Font is ");
    sbr.Append("Inconsolata");
    //Now sbr="My Favourite Programming Font is Inconsolata"
    Console.ReadKey();
  }
}
C#
c# string is immutable

in line 11 content of str changes, so new instance is created in a different memory location with new value as shown in above image.

Even Though line 14 changes the value of stringbuilder variable sbr it won’t create a new instead it will keep appending new strings to existing instance.see how it looks in terms of memory

c# stringbuilder is mutable

Because of this behavior of StringBuilder it also known as Mutable String.

I am not convinced

If you say I’m not convinced. let us check these behaviours using  C# code snippet. For that I am using C# class ObjectIDGenerator(in System.Runtime.Serialization Namespace). Actually it will return an unique integer value for instances that we created in our programs.With the help of this class we can check whether new instance is created or not for various operations on string and stringbuilder .Consider following program.

using System;
using System.Text;
using System.Runtime.Serialization;

class Program
{
  static void Main(string[] args)
  {
    ObjectIDGenerator idGenerator = new ObjectIDGenerator();
    bool blStatus = new bool();
    //just ignore this blStatus Now.
    String str = "My first string was ";
    Console.WriteLine("str = {0}", str);
    Console.WriteLine("Instance Id : {0}", idGenerator.GetId(str, out blStatus));
    //here blStatus get True for new instace otherwise it will be false
    Console.WriteLine("this instance is new : {0}\n", blStatus);
    str += "Hello World";
    Console.WriteLine("str = {0}", str);
    Console.WriteLine("Instance Id : {0}", idGenerator.GetId(str, out blStatus));
    Console.WriteLine("this instance is new : {0}\n", blStatus);
    //Now str="My first string was Hello World"
    StringBuilder sbr = new StringBuilder("My Favourate Programming Font is ");
    Console.WriteLine("sbr = {0}", sbr);
    Console.WriteLine("Instance Id : {0}", idGenerator.GetId(sbr, out blStatus));
    Console.WriteLine("this instance is new : {0}\n", blStatus);
    sbr.Append("Inconsolata");
    Console.WriteLine("sbr = {0}", sbr);
    Console.WriteLine("Instance Id : {0}", idGenerator.GetId(sbr, out blStatus));
    Console.WriteLine("this instance is new : {0}\n", blStatus);
    //Now sbr="My Favourate Programming Font is Inconsolata"
    Console.ReadKey();
  }
}
C#

Output Will look like this

Output - string vs stringbuilder in c#

Did you see that..?, Instance id for string get changed from 1 to 2 when str concatenated with “Hello World”.while instance id of sbr remains same as 3 after append operation also. This tells all about mutability and immutability. blStatus variable indicate whether the instance is new or not. Now you are convinced right?

Who Run Faster?

Let’s discuss performance difference between string and stringbuilder.The following code box will explain things in a better way. Before that if you don’t know how to measure execution time in C# you can read my article from here .

using System;
using System.Text;
using System.Diagnostics;

class Program
{
  static void Main(string[] args)
  {
    Stopwatch Mytimer = new Stopwatch();
    string str = string.Empty;
    Mytimer.Start();
    for (int i = 0; i < 10000; i++)
    {
        str += i.ToString();
    }
    Mytimer.Stop();
    Console.WriteLine("Time taken by string : {0}", Mytimer.Elapsed);
    StringBuilder sbr = new StringBuilder(string.Empty);
    //restart timer from zero
    Mytimer.Restart();
    for (int i = 0; i < 10000; i++)
    {
        sbr.Append(i.ToString());
    }
    Mytimer.Stop();
    Console.WriteLine("Time taken by stringbuilder : {0}", Mytimer.Elapsed);
    Console.ReadKey();
  }
}
C#

Output of this program was

string vs stringbuilder perfomance

This output clearly shows their performance difference.StringBuilder is about 70X faster than String in my laptop. it might be different in your case but generally speaking stringbuilder gives 10x times speed than string.

One Last Thing

New Instance of string will be created only when it's value changes

One more thing,If you do an operation on a string variable, Creation of new instance occurs only when it's current value changes.try following code,

using System;
using System.Text;
using System.Runtime.Serialization;

class Program
{
  static void Main(string[] args)
  {
    ObjectIDGenerator idGenerator = new ObjectIDGenerator();
    bool blStatus = new bool();
    string str = "Fashion Fades,Style Remains Same";
    Console.WriteLine("initial state");
    Console.WriteLine("str = {0}", str);
    Console.WriteLine("instance id : {0}", idGenerator.GetId(str, out blStatus));
    Console.WriteLine("this is new instance : {0}", blStatus);
    //a series of operations that won't change value of str
    str += "";
    //try to replace character 'x' which is not present in str so no change
    str = str.Replace('x', 'Q');
    //trim removes whitespaces from both ends so no change
    str = str.Trim();
    str = str.Trim();
    Console.WriteLine("\nfinal state");
    Console.WriteLine("str = {0}", str);
    Console.WriteLine("instance id : {0}", idGenerator.GetId(str, out blStatus));
    Console.WriteLine("this is new instance : {0}", blStatus);
    Console.ReadKey();
  }
}
C#

Output!

difference between string and stringbuilder

initial and final id is 1-means new instance is not created for these operation since it does not change the value of str. You may wonder about how compiler shows this much intelligence.

Middleware in ASP.NET 6 - Conditionally Adding Middleware to the Pipeline

Leave a Comment

 In this final part of the series, we will show two ways to conditionally execute middleware in the pipeline: by using settings in the AppSettings.json file to determine whether or not to add the middleware to the pipeline in the first place, or by using the incoming request's data to conditionally execute a middleware piece already in the pipeline.

This way, or that way? Photo by Sigmund / Unsplash

Conditional Middleware based on AppSettings

Remember the TimeLoggingMiddleware class from Part 3? If you don't, here it is again.

using MiddlewareNET6Demo.Logging;
using System.Diagnostics;

namespace MiddlewareNET6Demo.Middleware
{
    public class TimeLoggingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILoggingService _logger;

        public TimeLoggingMiddleware(RequestDelegate next, 
                                     ILoggingService logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();

            await _next(context);

            watch.Stop();
            _logger.Log(LogLevel.Information, "Time to execute: " + watch.ElapsedMilliseconds + " milliseconds.");
        }
    }
}

We're going to say, in this post, that we only want to add the TimeLoggingMiddleware to the application pipeline under certain conditions. Those conditions might be when we're tracking down a bug, or the customer has complained of slowness in the app, or something else.

In order to conditionally add a piece of middleware to the pipeline, it's convenient to set up a section in our AppSettings.json file that can be read by the Program.cs file.

Let's add a section called MiddlewareSettings, and a property called UseTimeLoggingMiddleware, to our AppSettings.json file.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "MiddlewareSettings": {
    "UseTimeLoggingMiddleware": "true",
  }
}

We also need a C# class that can hold the value of these settings. By convention, the class name should match the section name MiddlewareSettings, and the property name should match the item name UseTimeLoggingMiddleware.

namespace MiddlewareNET6Demo
{
    public class MiddlewareSettings
    {
        public bool UseTimeLoggingMiddleware { get; set; }
    }
}

Then, in our Program.cs file, we can read that section of AppSettings.json and map it to an instance of the MiddlewareSettings C# class:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddTransient<ILoggingService, LoggingService>();

var app = builder.Build();

var middlewareSettings = builder.Configuration.GetSection("MiddlewareSettings").Get<MiddlewareSettings>();

//...Rest of Program.cs

Using the new middlewareSettings instance, we can add the TimeLoggingMiddleware to the pipeline only if UseTimeLoggingMiddleware is true:

//...Rest of Program.cs

if(middlewareSettings.UseTimeLoggingMiddleware)
    app.UseTimeLoggingMiddleware();
    
//...Rest of Program.cs

In this way, we can control which middleware is active in the pipeline based on application-wide settings. This might make it easier, for example, to log execution times when trying to improve app performance.

Conditional Middleware based on Request URL

This next way to conditionally run middleware is a bit of a cheat; the middleware here will always be added to the pipeline, but will not always do anything other than pass execution to the next middleware piece.

Say we have a new middleware class called CultureMiddleware:

using System.Globalization;

namespace MiddlewareNET6Demo.Middleware
{
    public class CultureMiddleware
    {
        private readonly RequestDelegate _next;

        public CultureMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            var cultureQuery = context.Request.Query["culture"];
            if (!string.IsNullOrWhiteSpace(cultureQuery))
            {
                var culture = new CultureInfo(cultureQuery);

                CultureInfo.CurrentCulture = culture;
                CultureInfo.CurrentUICulture = culture;
            }
            await _next(context);
        }
    }
}

Note that this middleware only does some actual work if there exists a culture request parameter in the incoming request. If that parameter does exist, the middleware sets the current culture of the app to the incoming parameter's value.

For example, if we submitted the following request:

http://ourDemoSite.com/Users/174/Details?culture=fr-FR

The CultureMiddleware would set the culture of the app to fr-FR, which is the culture for France, and then normal processing would take the user to whatever page or location is represented by the Users/174/Details portion of the URL.

If the request was

http://ourDemoSite.com/Invoices/Details/1235267376?culture=uk

Then the middleware sets the culture to uk (Ukrainian) and then processes normally.

If, on the other hand, the incoming request was

http://ourDemoSite.com/Subscriptions/v4nd37c/matthew-jones

Then the middleware, effectively, does nothing, and the culture of the app remains the default culture.

We can use the entirety of the incoming Request for this kind of logic. We could, for example, only execute the code for the TimeLoggingMiddleware if the incoming request contained certain values. If we were using an MVC app, for example, maybe we only want to record execution times for a single controller. In Razor Pages, maybe it's only a certain page that's having problems. Maybe only one endpoint in an API is slow. In any of these cases, we could use this methodology to target the TimeLoggingMiddleware to exactly what we want to record.

In short, this method of conditionally executing middleware gives us finer-grain control over what to execute than the AppSettings.json solution does, at the potential cost of always needing to insert the middleware into the pipeline.

Thank You for Reading!

Thanks for reading this series! 

Middleware in ASP.NET 6 - Order of Operations

Leave a Comment

 Requests flow through the middleware in order, and responses bubble up through the middleware in reverse order.

At this point in this series, we have defined two middleware classes: LoggingMiddleware for request/response logging, and SimpleResponseMiddleware which can short-circuit the pipeline to return a response.

In this post, we will start with only LoggingMiddleware in the pipeline:

//...Rest of Program.cs

app.UseLoggingMiddleware();

//...Rest of Program.cs

Adding a Delay

Let's imagine that we now have a new middleware class, called IntentionalDelayMiddleware, which looks like this:

namespace MiddlewareNET6Demo.Middleware
{
    public class IntentionalDelayMiddleware
    {
        private readonly RequestDelegate _next;

        public IntentionalDelayMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            await Task.Delay(100);

            await _next(context);

            await Task.Delay(100);
        }
    }
}

As you can see, this middleware class waits 100ms both when processing the incoming request and when processing the outgoing response, for a total wait time of 200ms.

This is clearly not something we would want to do in the real world. For this post, we're not operating in the real world. Here, IntentionalDelayMiddleware represents some kind of undefined middleware that takes a predictable amount of time to execute.

We need to add an instance of IntentionalDelayMiddleware to the pipeline. The question is: do we add it before or after the LoggingMiddleware?

In this case, it most likely doesn't matter, since the two middleware classes don't interact and don't process the same thing. For this demo, let's add the IntentionalDelayMiddleware AFTER the LoggingMiddleware:

//...Rest of Program.cs

app.UseLoggingMiddleware();
app.UseIntentionalDelayMiddleware();

//...Rest of Program.cs

If we run the app now, we won't really notice a significant difference; 200ms is pretty quick.

Adding an Execution Time Middleware

But now there's a wrinkle (you knew there had to be). Imagine that we get a new requirement from our manager. S/he says that we need to log the execution time of every request to our system.

This is actually pretty straightforward to do with middleware; it uses the Stopwatch class provided by .NET and the LoggingService class we created in Part 2 of this series. Here's the middleware class, named TimeLoggingMiddleware:

using MiddlewareNET6Demo.Logging;
using System.Diagnostics;

namespace MiddlewareNET6Demo.Middleware
{
    public class TimeLoggingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILoggingService _logger;

        public TimeLoggingMiddleware(RequestDelegate next, 
                                     ILoggingService logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();

            await _next(context);

            watch.Stop();
            _logger.Log(LogLevel.Information, "Time to execute: " + watch.ElapsedMilliseconds + " milliseconds.");
        }
    }
}

We need to add this to the pipeline. But again, that question remains: exactly where should we do so?

If we add the TimeLoggingMiddleware to the pipeline BEFORE the IntentionalDelayMiddleware, the delay caused by the latter will be included in the measurements taken by the former. If we add it AFTER, the delay will not be included, but is that an accurate measurement, especially if the delay in IntentionalDelayMiddleware changes?

In fact, let's expand our look at the pipeline:

//...Rest of Program.cs

app.UseHttpsRedirection();
app.UseStaticFiles();

//We can also use custom extensions to add middleware to the pipeline.
//Note that if this middleware causes any delay, that delay is
//NOT included in the time logs.
app.UseLoggingMiddleware();

//Here's the time logging middleware
app.UseTimeLoggingMiddleware();

//Here's the delay. At the moment, the delay is INCLUDED in the time logs.
app.UseIntentionalDelayMiddleware();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Given this Program.cs file, where might be a more appropriate place to put the TimeLoggingMiddleware? That answer depends on several questions, such as:

  • Do the time logs need to include execution time for things like invalid authorizations? If so, the TimeLoggingMiddleware must be included BEFORE the call to app.UseAuthorization().
  • Routing a call takes a very small, but measurable, amount of time. Do we want to include that? If so, the TimeLoggingMiddleware must be included BEFORE the call to app.UseRouting().

Like most real-world problems, there's no clear answer here. Given no other direction, I personally would err on the side of NOT including the known delay in the measurement, but this is ultimately a decision that needs to be made by individual developers using their system's known quirks, rules, and guidelines.

The key part that you need to remember is that THIS:

//...Rest of Program.cs

app.UseIntentionalDelayMiddleware();
app.UseTimeLoggingMiddleware();

//...Rest of Program.cs

is potentially very different from THIS:

//...Rest of Program.cs

app.UseTimeLoggingMiddleware();
app.UseIntentionalDelayMiddleware();

//...Rest of Program.cs

This is one example of why the order of the middleware in the pipeline matters.