6 lesser-known features of C# / .NET that you should be using

Introduction

This post is going to setup 6 features of the .NET Framework which I feel are under-utilised by a lot of developers – your opinion on whether they are under-utilised may differ from mine, but I hope some of you will find this post useful.

1. Stopwatch

So I’m going to start with something we’ll use further on, the Stopwatch. It’s quite likely that at some point, you’ll have reason to want to profile parts of your code to find any performance bottlenecks. While there are a plethora of benchmarking packages you can use in your code (Benchmark.NET being one of the most popular), sometimes you just want to benchmark something quickly without any fuss. I imagine most people would do something like the below

var start = DateTime.Now;
Thread.Sleep(2000); //Code you want to profile here
var end = DateTime.Now;
var duration = (int)(end - start).TotalMilliseconds;
Console.WriteLine($"The operation took {duration} milliseconds");

This works – it’ll tell you it took ~2000 milliseconds. However, this is not the recommended way of benchmarking because DateTime.Now may not give you the required level of precision – DateTime.Now is usually accurate to roughly 15 milliseconds. To demonstrate this, see the very contrived example below:

var sleeps = new List<int>() { 5, 10, 15, 20 };
foreach (var sleep in sleeps)
{
	var start = DateTime.Now;
	Thread.Sleep(sleep);
	var end  = DateTime.Now;
	var duration = (int)(end - start).TotalMilliseconds;
	Console.WriteLine(duration);
}

The output will likely change every time, but you’ll likely get something like this:

15
15
15
31

So we’ve established it’s not very precise, but what if you don’t care? You can of course carry on using the DateTime.Now method of benchmarking, but there’s a much nicer alternative called the Stopwatch which sits in the System.Diagnostics namespace. It’s much neater than using DateTime.Now, and expresses your intent much more succinctly – and it’s much more accurate! Let’s change our last bit of code to use the Stopwatch class:

var sleeps = new List<int>() { 5, 10, 15, 20 };
foreach (var sleep in sleeps)
{
    var sw = Stopwatch.StartNew();
    Thread.Sleep(sleep);
    Debug.WriteLine(sw.ElapsedMilliseconds);
}

Our output is then this (of course, it is variable)

6
10
15
20

Much better! So really, given the ease of using the Stopwatch class, there is really no reason to use the ‘old fashioned’ way.

2. Task Parallel Library (TPL)

var items = Enumerable.Range(0,100).ToList();
var sw = Stopwatch.StartNew();
foreach (var item in items)
{
	Thread.Sleep(50);
}
Console.WriteLine($"Took {sw.ElapsedMilliseconds} milliseconds..."); 

This takes, as you’d expect, roughly 5000 millseconds/5 seconds (100 * 50 = 5000). Now let’s see our parallel version using the TPL…

var items = Enumerable.Range(0,100).ToList();
var sw = Stopwatch.StartNew();
Parallel.ForEach(items, (item) => 
{
	Thread.Sleep(50);					 
});
Console.WriteLine($"Took {sw.ElapsedMilliseconds} milliseconds..."); 

This takes, on average, just 1000 milliseconds – which is a 500% improvement! Results will differ depending on your setup, but it’s likely you’ll see a similar improvement to what I have. And notice how simple the loop is – it’s barely any more complicated than a regular foreach loop.

But…if you operate on a non thread safe object inside the loop, then you’re gonna have a bad time. So you can’t just go replacing any foreach you see fit! Again, see my article on the TPL for some tips on this.

3. Expression Trees

Expression Trees are an extremely powerful part of the .NET Framework, yet they are also one of the most poorly understood parts (by non-expert programmers) too. It took me a long time to fully grasp the concept of these, and I’m still far from an expert on the matter, but essentially it allows you to wrap a lambda expression, such as Func<T> or Action<T>, and analyze the actual lambda expression itself. It’s probably best to illustrate this with an example – and .NET Framework has plenty of these, especially in LINQ to SQL and Entity Framework.

The ‘Where’ extension method in LINQ to Objects takes a Func<T,int, bool> as its main parameter – see the below code which I have stolen from Reference Source (which contains the .NET source code)

static IEnumerable<TSource> WhereIterator<TSource>(IEnumerable<TSource> source, Func<TSource, int, bool> predicate) 
{
     int index = -1;
     foreach (TSource element in source) 
     {
          checked { index++; }
          if (predicate(element, index)) yield return element;
     }
}

This is as you’d expect – it iterates through the IEnumerable and yields what matches the predicate. However, clearly this would not work in LINQ to SQL/Entity Framework – it needs to translate your predicate to SQL! So the signature for the IQueryable version is a bit different…

static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, int, bool>> predicate) 
{
      return source.Provider.CreateQuery<TSource>( 
                Expression.Call(
                    null,
                    GetMethodInfo(Queryable.Where, source, predicate),
                    new Expression[] { source.Expression, Expression.Quote(predicate) }
                    ));
}

If you look past the daunting nature of the method, you’ll notice that the function now takes the Func<T,int,bool> wrapped in an Expression – this essentially allows the LINQ ‘provider’ to read through the Func to see exactly what predicate was passed through, and translate it to SQL. So in essence, Expressions allow you to examine your code at runtime.

Let’s quickly look at something a bit simpler – imagine you have the below code that adds settings to a dictionary (we used to use this in some real code)

var settings = new List<Setting>();
settings.Add(new Setting("EnableBugs",Settings.EnableBugs));
settings.Add(new Setting("EnableFluxCapacitor",Settings.EnableFluxCapacitor));

I hope it’s not difficult to see the danger/repetition here – the name of the setting in the dictionary is a string, and a typo here could cause some issues. Plus, it’s tedious to have to do this! If we create a new method that takes an Expression<Func<T>> (essentially, takes a lambda expression that returns something), we get the actual name of the variable passed in!

private Setting GetSetting<T>(Expression<Func<T>> expr)
{
	var me = expr.Body as MemberExpression;
	if (me == null)
             throw new ArgumentException("Invalid expression. It should be MemberExpression");

        var func = expr.Compile(); //This converts our expression back to a Func
	var value = func(); //Run the func to get the setting value
	return new Setting(me.Member.Name,value);
}

We can then call it as below…

var settings = new List<Setting>();
settings.Add(GetSetting(() => Settings.EnableBugs));
settings.Add(GetSetting(() => Settings.EnableFluxCapacitor));	

Much nicer! You’ll note that in our GetSetting method, I need to check if the expression passed in as a ‘MemberExpression’ – this is because there’s nothing stopping calling code from passing something like a method call or a constant value in, in which case there is no ‘member name’.

Obviously I am scratching the surface of what Expressions are capable of, and I hope to write a future post detailing this

4. Caller Information Attributes

Caller Information attributes were introduced in .NET Framework 4.0, and while they aren’t useful in most cases, they really show their worth when writing code to log debugging information. Let’s imagine you have crude Log function as defined below:

public static void Log(string text)
{
   using (var writer = File.AppendText("log.txt"))
   {
       writer.WriteLine(text);
   }
}

So you can call this at various points in your code to log some information to the text file, however a single string may not be very useful to you when debugging, unless you’ve made the effort to put sufficient detail into the string. What you’ll likely want is where the log entry occurred – i.e. what method. You can do this by examining the stack trace as below

public static void Log(string text)
{
   using (var writer = File.AppendText("log.txt"))
   {
       writer.WriteLine($"{text} - {new StackTrace().GetFrame(1).GetMethod().Name});
   }
}

This works – it will print ‘Main’ if I call this from my Main method. However, it is slow and inefficient as you’re essentially capturing the stack trace as you would if an exception was thrown. .NET Framework 4.0 introduces the aforementioned ‘caller information attributes’, which allows the Framework to automatically tell your method information about what’s calling it – specifically the file path, method/property name and line number. Essentially, you use these by allowing your method to take optional string parameters that you mark with an attribute. See below where I use the 3 available attributes

public static void Log(string text,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
	Console.WriteLine($"{text} - {sourceFilePath}/{memberName} (line {sourceLineNumber})");	
}

This does exactly as you’d expect – it outputs the path to the source file, the method it’s called from and also the line number. Obviously, this information (especially the latter two) are very useful for debugging purposes. And this has no impact on speed at all, because the information is actually passed through as static values at compile time. The only downside is that it pollutes your method signature with these optional values, when you never really want any caller to supply them automatically. But I think that’s a small price to pay

5. The ‘Path’ class

This one may be a bit more well known, but I still see developers doing things like getting the file extension of a file name manually, when the Path class has built in and tried and tested methods to do this for you. The class lives in the System.IO namespace, and contains many helpful methods that reduces the amount of boilerplate code you have to write. A lot of you will be familar with methods such as Path.GetFileName and Path.GetExtension (which do just as you’d expect), but I’ll mention some slightly more obscure ones below

Path.Combine

This method takes 2,3 or 4 paths and combines them into one. Generally, people do this to append a filename to a directory path such as directoryPath + “\” + filename . The problem with that is that you making the assumption that ‘\’ is the directory separator on the system your application is running on – what if the application is running on Unix, which uses a forward slash (‘/’) as its directory separator? This is becoming more of a concern as .NET Core is allowing .NET applications to run on many more platforms now. Path.Combine will use the directory separator applicable for the operating system, and will also deal with redundant separators – i.e. if you append a directory with ‘\’ at the end to a filename with ‘\’ at the start, Path.Combine will strip one out. You can see more reasons why you should use Path.Combine here

Path.GetTempFileName

Quite often, you’ll need to write a file that you only need access to temporarily. You don’t care about the name or where it’s stored, but you just need to write to it and soon after read from it.

While you could write code to manage this yourself, it’s going to be tedious and error prone. Enter the insanely useful Path.GetTempFileName method in the Path class – it takes no parameters, but it creates an empty file in the users defined temporary directory and returns the full path for you to use. As it’s in the users temporary directory, Windows will automatically maintain this and you won’t need to worry about clogging the system with redundant files. See this article for some information on this method, plus the related ‘GetTempPath’

Path.GetInvalidPathChars / Path.GetInvalidFileNameChars

Path.GetInvalidPathChars, and its brother Path.GetInvalidFileNameChars, returns an array of all characters that are invalid as paths/filenames on the current system. I’ve seen so much code that manually strips out some of the most common invalid characters such as quotes but not any of the other invalid characters, which is a disaster waiting to happen. And in the spirit of cross platform compatibility, it’s incorrect to assume that what is invalid on one system will be invalid on another. My only criticism of these methods is that they do not provide a way of checking if a path contains any of these characters, which usually involves me writing the below ‘boiler plate’ methods

public static bool HasInvalidPathChars(string path)
{
	if (path  == null)
		throw new ArgumentNullException(nameof(path));
	
	return path.IndexOfAny(Path.GetInvalidPathChars()) >= 0;
}
	
public static bool HasInvalidFileNameChars(string fileName)
{
	if (fileName == null)
		throw new ArgumentNullException(nameof(fileName));
	
	return fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0;
}

6. StringBuilder

String concatenation in .NET is very common, but can also be very inefficient if not done properly. Because strings are immutable, any string concatenation results in a new string being returned for every concatenation. For a small number of concatenations, this difference will be minimal, but performance can quickly deteriorate.

Enter the aptly named StringBuilder class, which allows you to concatenate with minimal performance overhead. How it does this is fairly straightforward – on a high level, it’s storing a list of every character you append, and only building up your string once you actually need it. For a demonstration of how to use it, plus the performance benefits, see the below code that benchmarks both of these (using the useful Stopwatch class I mentioned earlier)

var sw = Stopwatch.StartNew();
string test = "";
for (int i = 0; i < 10000; i++)
{
	test += $"test{i}{Environment.NewLine}";	
}
Console.WriteLine($"Took {sw.ElapsedMilliseconds} milliseconds to concatenate strings using string concatenation");
		
sw = Stopwatch.StartNew();
var sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
	sb.Append($"test{i}");
	sb.AppendLine();
}
Console.WriteLine($"Took {sw.ElapsedMilliseconds} milliseconds to concatenate strings using StringBuilder");

The results of this benchmark are below:

Took 785 milliseconds to concatenate strings using the string concatenation
Took 3 milliseconds to concatenate strings using StringBuilder

Wow – that is over 250x faster! And this is for only 10000 concatenations – if you’re building up a large CSV file manually (which is bad practice anyway but let’s not worry about that), your figure may be larger. If you are only concatenating a couple of strings, it’s probably fine to use concatenate without using StringBuilder, but to be honest I like to get in the habit of always using it – the cost of newing up the StringBuilder is, relatively speaking, minuscule.

In my code sample, I am using sb.Append followed by sb.AppendLine – you can actually just call sb.AppendLine, passing through your text you want to append, and it’ll add a new line at the end. I wanted to include Append and AppendLine just to make it a bit clearer.

Conclusion

I imagine to any seasoned professional, most of the above will be familiar to you. Personally, however, it took me years to find out about all of the above, so I hope this article helps out some of the less experienced developers out there and helps avoid wasting time by writing boilerplate and potentially buggy code that Microsoft have already kindly done for us!

I’d love to hear from anyone who uses other underused features in .NET/C#, so please comment below if so!

error