5 CSharp Tips That you may already know
5 CSharp tips that will help you write more clean/efficient code
5 CSharp tips that you may not know
I acknowledge that you may already know most of these tips already. But I write in the hope that if someone in the community can benefit from learnings. If you want to find out if there is anything useful for you in this post have a quick look at the headings.
Note to all readers
I want to make clear that i do not think of me as an expert of the topic. I understand few of you find out these tips are not correct or not inline with your thinking/opinion. These tips will serve better to individuals who will look at them as starting point and continue their own exploration/learning and may end up finding that these tips were not the best way to do things and find much better ways. If you are still interested and think you have 2 mins to spare please go ahead and you may find something useful.
1.Avoid reading the Http response to a temporary string
Recently i read a tweet on twitter from David Fowler
criticizing reading whole Http response to string a pattern that he observed being used by many developers in the community. Then i went out on a pursuit to find out why it is so bad. I understand there is temporary allocation involved and more un-necessary allocations more work for garbage collector
that can adversely effect your applications. But that’s not the whole story there is a possibility of even bigger issue in such allocation.
Http request Responses can be sometime really big in size and can even go above the limit of the 85kb. If they go above the limit of 85kb in size they will be allocated on LOH(Large object heap) that is bad enough news to avoid these sort of un-necessary allocations. Want to read more about the danger of Large object heap why you should try to avoid where possible. Here is an article about how Large Object Heap Works
Below is an example how you can avoid reading response of an Http Request into a temporary string for De-serialization this example uses JSON.NET
for Serialization processing.
Credit for this code sample goes to JSON.NET documentation website with few changes
var client = new HttpClient();
using (var s = await client.GetStreamAsync("http://www.nerdlife.me"))
using (var sr = new StreamReader(s))
using (var reader = new JsonTextReader(sr))
{
var serializer = new JsonSerializer();
// Read the json response for the request from the stream
// Json size doesn't matter because only a small piece is read at a time from the HTTP request
var p = serializer.Deserialize<Person>(reader);
}
2.Replace temporary/un-necessary collections with Yield
This is fairly common scenario that we come across while writing applications. In this scenario we have a method that will return a collection
.
[TestMethod]
public void TestWithoutYield()
{
// Display powers of 2 up to the exponent of 8:
foreach (int i in PowerWithoutYield(2, 8))
{
Debug.Write("{0} ", i);
}
}
public static IEnumerable<int> PowerWithoutYield(int number, int exponent)
{
int result = 1;
var results = new List<int>();
for (int i = 0; i < exponent; i++)
{
result = result * number;
results.Add(result);
}
return results;
}
In this example we created a method that Raise the first parameter to the power of second parameter. What we did we created a temporary collection to new data and added new values to this temporary collection and returned the collection.
We can use yield
to eliminate the need of these upfront temporary allocations that was used for the resulting list (var results = new List<int>()
).
If we refactor the above code snippet to use Yield.The above code sample will change to something like below.
“Code example is inspired from MSDN website”
[TestMethod]
public void TestWithYield()
{
// Display powers of 2 up to the exponent of 8:
foreach (int i in PowerWithYield(2, 8))
{
Debug.Write("{0} ", i);
}
}
public static IEnumerable<int> PowerWithYield(int number, int exponent)
{
int result = 1;
for (int i = 0; i < exponent; i++)
{
result = result * number;
yield return result;
}
}
Have you noticed how we do not need a temporary upfront list to hold new numbers from the method PowerWithYield
.
By temporary allocation means some allocation that we just used to hold return results and was not serving any other purpose.
Avoiding upfront temporary allocations results in efficient utilization of available memory of your application. But avoiding upfront temporary allocations is not the only benefit of Yield there is more to it sometimes it can even result in avoiding few un-necessary computations or processing as it results in deferred execution. But explaining everything about yield
and how it works deserves a post on its own.
From the feedback of very few readers it looks like people find it hard to understand how it can avoid allocation. I created two gists to demonstrate the difference between them. We will analyze the Decompiled code to find out how actually Yield
can help avoid the allocation.
- Here is the link for the Non Yield Example
As you can see there is a collection to hold the results of the function execution and this temporary collection can be avoided using Yield
keyword.
In order to highlight the difference i created another gist that show the Decompiled
code for the Yield
example. And you can see in the Gist that Yield
method is converted into a StateMachine
that implements IEnumerable
interface and Then calling method get the enumerator of StateMachine
using GetEnumerator
and Use move next to iterate through the values that will be returned by the Yield
method. So we avoided an allocation for the Intermediate/Temporary list to hold return results.
- Here is the link for the Yield Example
There are situations where using Yield will result in overhead that will overweight the benefits of avoiding some upfront temporary allocations. Specially when the collection you are going to return from a method is small in size. In that case cost of creating StateMachine will overweight the upfront cost of Temporary allocations. But you can benefit if you are going to return a huge collection in that case you can avoid the allocation cost of List to hold results.
I just highlighted one of the benefits that ‘yield’ can offer in some scenarios. But highly encourage you to explore more. I will leave you with something that may encourage to explore more about the yield
and how it works in C#.
By looking at the code what you think whether the
Unit Test
will pass or fail due tounhandeled exception
?
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CSharpTipsProject
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void GeEvenNumbersWhereYieldReturnThrowException()
{
var numbersToFilter = Enumerable.Range(1, 20);
var evenNumbers = GetEvenNumbersWithYield(numbersToFilter);
Assert.IsNotNull(evenNumbers);
}
private IEnumerable<int> GetEvenNumbersWithYield(IEnumerable<int> numbers)
{
Debug.WriteLine("Filtering Even Numbers");
throw new Exception("Exception in get even numbers.");
foreach (var num in numbers)
{
Debug.WriteLine("Processing input number: "+num);
if (num % 2 == 0)
{
yield return num;
}
}
}
}
}
3.Marking Code as Obsolete
If you ever came across a situation where you have some functionality available in a library or application. That you are not ready to remove completely yet. But you want to discourage the use of that functionality.
Then CSharp
already provides a attribute
that allows to decorate methods and compiler will warn callers about depreciation and discourage them to use it.
If you want to just show the warning to the consumer with a user friendly message you can use something like below. When user will compile the code compiler will generate a build warning.
For Warning:
[Obsolete("This method has been superseded by the IsInUse() method")]
public void NotInUse()
If you want to completely disallow the use of a given functionality. You can pass the second parameter as ‘true’ to the Obsolete
attribute and it will break the compilation of the code. Piece of code using the obsolete method will not be compiled without errors.
For Error:
[Obsolete("This method has been superseded by the IsInUse() method", true)]
public void NotInUse()
4. Preserving stack trace when re-throwing exceptions
You must have written a piece of code in your application. Where you have try and catch block and inside the catch block instead of handling the exception. You read the exception data Debug Log somewhere and then rethrow the exception so it can bubble up call stack and then you handle that exception at some central place in your application.
In order to demonstrate this feature I created a dummy exception factory that will throw exception and code for the factory and it looks like
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace RethrowExample
{
public class ExceptionThrowingFactory
{
//Method that will loose the stack trace
public void NoStackTrace()
{
try
{
FirstLevelDeep();
}
catch (Exception ex) //Typical try catch pattern
{
Debug.WriteLine($"Exception with message: {ex.Message}");
throw ex;
}
}
//Method that will preserve the stack trace
public void WithStackTrace()
{
try
{
FirstLevelDeep();
}
catch (Exception ex) //Typical try catch patter
{
Debug.WriteLine($"Exception with message: {ex.Message}");
throw;
}
}
//Dummy method to make call stack 3 levels deep
public void FirstLevelDeep()
{
SecondLevelDeep();
}
public void SecondLevelDeep()
{
ThreeLevelDeep();
}
public void ThreeLevelDeep()
{
throw new Exception("Just a demo exception.");
}
}
}
Now we are going to call the both methods NoStackTrace
and WithStackTrace
from the factory class by wrapping them into a unit test
.
[TestClass]
public class TestStackTrace
{
[TestMethod]
public void TestStackTraceLostForExceptions()
{
var factory = new ExceptionThrowingFactory();
factory.NoStackTrace();
}
[TestMethod]
public void TestStackTracePreservedForExceptions()
{
var factory = new ExceptionThrowingFactory();
factory.WithStackTrace();
}
}
Let’s debug these both tests one by one. First we execute the Test Case TestStackTraceLostForExceptions
and we will look at the resulting StackTrace
.
//Original stack for the failed method
Result StackTrace:
at RethrowExample.ExceptionThrowingFactory.NoStackTrace() in C:\Users\ranje\Documents\Visual Studio 2017\Projects\RethrowExample\RethrowExample\ExceptionThrowingFactory.cs:line 20
at RethrowExample.TestStackTrace.TestStackTraceLostForExceptions() in C:\Users\ranje\Documents\Visual Studio 2017\Projects\RethrowExample\RethrowExample\TestStackTrace.cs:line 12
As we can see the StackTrace
suggest that the exception happened in the method NoStackTrace()
and we lost the original call stack information from where exception was originated.
Now let’s debug the second Test Case TestStackTracePreservedForExceptions
and have a look at the StackTrace
for exception captured.
Result StackTrace:
at RethrowExample.ExceptionThrowingFactory.ThreeLevelDeep() in C:\Users\ranje\Documents\Visual Studio 2017\Projects\RethrowExample\RethrowExample\ExceptionThrowingFactory.cs:line 48
at RethrowExample.ExceptionThrowingFactory.SecondLevelDeep() in C:\Users\ranje\Documents\Visual Studio 2017\Projects\RethrowExample\RethrowExample\ExceptionThrowingFactory.cs:line 43
at RethrowExample.ExceptionThrowingFactory.FirstLevelDeep() in C:\Users\ranje\Documents\Visual Studio 2017\Projects\RethrowExample\RethrowExample\ExceptionThrowingFactory.cs:line 38
at RethrowExample.ExceptionThrowingFactory.WithStackTrace() in C:\Users\ranje\Documents\Visual Studio 2017\Projects\RethrowExample\RethrowExample\ExceptionThrowingFactory.cs:line 27
at RethrowExample.TestStackTrace.TestStackTracePreservedForExceptions() in C:\Users\ranje\Documents\Visual Studio 2017\Projects\RethrowExample\RethrowExample\TestStackTrace.cs:line 19
Now we can see the stack trace contains the information about the complete call stack where the exception was originated from and information about the complete call stack for execution.
Summary of above discussion is:
//Using something like
// we will loose the stack trace information from the original exception
...
catch (Exception ex)
{
Debug.WriteLine($"Exception with message: {ex.Message}");
throw ex;
}
//Using just throw keyword preserve the original stack trace
...
catch (Exception ex)
{
Debug.WriteLine($"Exception with message: {ex.Message}");
throw;
}
5.Use your custom serializer and de-serializer for classes using JSON.NET
If you already have JSON.NET in your tech stack and don’t want any other serialization dependency but want to squeeze more performance out of the serialization and de-serialization using JSON.NET then you can think about writing custom serializer and de-serializer for your classes. Writing custom serializer will allow you to avoid the cost of Reflection used by JSON.NET but result will still may be slower than other serialization libraries that just focus on performance but that performance optimization come at the cost of loosing other benefits.
Writing custom serializer for your classes you need to write more code. But if there are one or two classes in your application that are used for Serialization and De-Serialization then you can write custom serializer for those classes only. If there are too many classes that need custom serializer and your application really focus on performance then look for alternative libraries that have better performance results as compared to JSON.NET.
The absolute fastest way to read and write JSON is to use JsonTextReader/JsonTextWriter directly to manually serialize types. Using a reader or writer directly skips any of the overhead from a serializer, such as reflection - (From JSON.NET Documentation Website)
Below is an example of writing custom serializer using JSON.NET
. Where you can use JsonTextReader
to read the String
data of a object and parse it into C# object of type Car
.
Note: The code below is for demonstration purposes not production ready.
public class Car {
public string Make {get;set;}
public string Model {get;set;}
public string Year {get;set;}
// other fields so on ...
}
//Example to De-Serialize Car Object string into an object
private static Car DeserializeCar(string carInfo)
{
using (var reader = new JsonTextReader(new StringReader(carInfo)))
{
string model;
var make = model = string.Empty;
int year = 0;
var currentProperty = string.Empty;
while (reader.Read())
{
if (reader.Value != null)
{
if (reader.TokenType == JsonToken.PropertyName)
currentProperty = reader.Value.ToString();
if (reader.TokenType == JsonToken.Integer && currentProperty == "Year")
year = Int32.Parse(reader.Value.ToString());
if (reader.TokenType == JsonToken.String && currentProperty == "Make")
make = reader.Value.ToString();
if (reader.TokenType == JsonToken.String && currentProperty == "Model")
model = reader.Value.ToString();
// Process other fields
}
}
return new Car(make, model, year);
}
}
//Example to Manually serialize the Car Object using extension method
public static string ConvertToJson(this Car c)
{
var sw = new StringWriter();
var writer = new JsonTextWriter(sw);
//Start writing serialized object
writer.WriteStartObject();
//Write the first property name
writer.WritePropertyName("make");
//Write the property value
writer.WriteValue(p.Make);
writer.WritePropertyName("model");
writer.WriteValue(p.Model);
writer.WritePropertyName("yodel");
writer.WriteValue(p.Year);
//Finish writing object add ending curly brace.
writer.WriteEndObject();
//Return the resulting serialized string
return sw.ToString();
}
Time to say good bye
I understand you may already know most of these tips already. But I write in the hope that if someone in the community can benefit from learnings. If you think this post helped you a bit please let me know in comments any feedback is much appreciated. If you like my posts and don’t want to miss my any future post please follow me on twitter at @NotRanjeet. I will see you guys soon with the a new post.