C# Coding Guidelines & Practices
This repo highlights the list of software engineering guidelines in general. Most of these are industry-wise conventions, thus using them will ensure that your code is easily readable by people who are not you.
It's a fact that coding standards are arbitrary. The key to a successful maintainable project, however, is not which standards you follow, but that you are consistent.
Just keep in mind that this post isn't about how you should indent your code (tabs vs spaces or curly braces placements), but it's more of a guidlines on how to write clean code that are easy to manage. With that said, if you are leading a team or even a single contributor developer who wanted to become better, having a set of coding guidelines is a great start to make that happen.
In this post, I will highlight the list of software engineering guidelines in general. Most of these are industry-wise conventions, thus using them will ensure that your code is easily readable by people who are not you.
Tip #1
bool result;
if (condition)
{
result = true;
}
else
{
result = false;
}
bool result = condition ? true: false;
The preceding code is much cleaner, easier to read and understand. On top of that, it's more concise.
Tip #2
if (something != null)
{
if (other != null)
{
return whatever;
}
}
return something?.other?.whatever;
The preceding code is also much cleaner and concise.
Tip #3
if (something != null)
{
if (other != null)
{
return whatever;
}
else
{
return string.empty;
}
}
else
{
return string.empty;
}
return something?.other?.whatever ?? string.empty;
Tip #4
int? number = null;
var n = number.HasValue ? number : 0;
var n = number ?? 0;
var n = number.GetValueOrDefault();
Tip #5
int? number = null;
if (number == null)
{
//do something
}
if (!number.HasValue)
{
//do something
}
int? number = null;
if (number is null)
{
//do something
}
Tip #6
if(conditioin) action;
Without the braces, it is too easy to accidentally add a second line thinking it is included in the if, when it isnβt.
if (condition) { action; }
//or better
if (condition)
{
action;
}
Tip #7
if (condition)
{
//do something
}
else if(condition)
{
//do something
}
else if(condition)
{
//do something
}
else(condition)
{
//do something else
}
switch(condition)
{
case 1:
//do something
break;
case 2:
//do something
break;
case 3:
//do something
break;
default:
//do something else
break;
}
condition switch
{
1 => //do something;
2 => //do something;
3 => //do something;
_ => //do something else;
}
The preceding code is more concise yet, still easy to read and understand. (Note, only available in C# 8 or later versions)
Tip #8
using (MemoryStream stream = new MemoryStream())
{
// do something
}
using var stream = new MemoryStream();
// do something
The preceding code reduces the number of curly braces in your method, but it can still be seen easily where a resource is disposed. For more information, see: "pattern-based using" and "using declarations"
Tip #9
string name = "Vianne";
string greetings = "Hello " + name + "!";
string name = "Vynn";
string greetings = string.Format("Hello {0}!", name);
string name = "Vjor";
string greeting = $"Hello, {name}!;
The preceding code is much more concise and readable compared to other approaches.
Tip #10
var date = DateTime.Now;
string greetings = string.Format("Today is {0}, the time is {1:HH:mm} now.", date.DayOfWeek, date);
var date = DateTime.Now;
string greetings = $"Today is {date.DayOfWeek}, the time is {date:HH:mm} now.");
The preceding code is much easier to understand and concise. However, there are certain cases that using the string.Format() would makes more sense. For example, when dealing with complex formatting and data manipulation. So, use your judgement when to apply them in situations.
Tip #11
List<Repository.DataAccessLayer.Whatever> listOfBlah = _repo.DataAccessLayer.GetWhatever();
var listOfBlah = _repo.DataAccessLayer.GetWhatever();
var students = new List<Students>();
var memoryStream = new MemoryStream();
var dateUntilProgramExpiry = DateTime.Now;
Tip #12
public string Greeter(string name)
{
return $"Hello {name}!";
}
public string Greeter(string name) => $"Hello {name}!";
The preceding code is more concise while maintaining readability.
Tip #13
Person person = new Person();
person.FirstName = "Vianne";
person.LastName = "Durano";
var person = new Person {
FirstName = "Vianne",
LastName = "Durano"
};
The preceding code is more natural to read and the intent is clear because the properties are defined within braces.
Tip #14
public Person GetName()
{
var person = new Person
{
FirstName = "Vincent",
LastName = "Durano"
};
return person;
}
public (string FirstName, string LastName) GetName()
{
return ("Vincent", "Durano");
}
The preceding code is more convenient for accessing objects and manipulating the data set. Tuples replaces the need to create a new class whose sole purpose is to carry around data.
Tip #15
string dateString = "40/1001/2021";
var isDateValid = DateTime.TryParse(dateString, our var date);
The preceding code is perfectly fine and should handle the conversion safely. However, the code is bit lengthy just to do basic conversion. Imagine you have tons of the same code conversion cluttering within the different areas in your project. Your code could turn into a mess or potentially causes you alot of development time overtime.
public static class DateExtensions
{
public static DateTime ToDateTime(this string value)
=> DateTime.TryParse(value, out var result) ? result : default;
}
and you will be able to use the extension method like in the following anywhere in your code:
var date = "40/1001/2021".ToDateTime();
The preceding code makes your code concise, easy to understand and provides convenience.
Tip #16
public class PersonManager
{
private readonly ILogger<PersonManager> _logger;
private readonly SomeConfiguration _config;
public PersonManager(ILogger<PersonManager> logger,
SomeConfiguration config)
{
_logger = whatever;
_config = config;
}
}
The preceding code is pretty much common for implementing dependency injection.
public class PersonManager
{
public PersonManager(ILogger<PersonManager> _logger,
SomeConfiguration _config)
{
}
}
The preceding code removes alot of noise in your code when injecting dependencies as you don't need to write private readonly declarations which can make your code cleaner.
In situations where you want to expose one of the fields to be public, you can define and set it in the constructor as what you would normally do. Otherwise, the arguments are marked as private fields.
Tip #17
String firstName;
Int32 orderCount;
Boolean isCompleted;
string firstName;
int orderCount;
bool isCompleted;
The preceding code is consistent with the Microsoftβs .NET Framework and makes code more natural to read.
Tip #18
private readonly PersonManager _pm;
The main reason for this is that it can cause confusion and inconsistency when you have class that might represents the same thing like in the following:
private readonly ProductManager _pm;
private readonly PersonManager _personManager;
private readonly ProductManager _productManager;
The preceding code provides more clarity as it clearly suggests what the object is about.
Tip #19
namespace ProjectName.App.Web
namespace ProjectName.Services.Common
namespace ProjectName.Services.Api.Payment
namespace ProjectName.Services.Api.Ordering
namespace ProjectName.Services.Worker.Ordering
The preceding code suggest good organization of your code within the project, allowing you to navigate between layers easily.
Tip #20
public class Person
{
//some code
}
public class BusinessLocation
{
//some code
}
public class DocumentCollection
{
//some code
}
This enables you to easily determine if an object holds a single item value or collection. Imagine, if you have a List vs List. It's just odd to put plural form names in a List or Collection.
Tip #21
public bool IsActive { get; set; }
public bool CanDelete { get; set; }
//variables
bool hasActiveSessions = false;
bool doesItemExist = true;
Adding those suffixes will provide more value to the caller.
Tip #22
public class ClassName
{
const int MaxPageSize = 100;
public string PropertyName { get; set; }
public void MethodName()
{
//do something
}
}
This is so that our code are consistent with the Microsoft .NET Framework.
Tip #23
public void MethodName(CreatePersonRequestDto requestDto)
{
var firstName = requestDto.FirstName;
}
This is so that our code are consistent with the Microsoft .NET Framework.
Tip #24
int daysUntilProgramExpiry;
public List<Person> GetPersonProfileById(long personId)
{
//do something
}
This makes your code easier to read and understand without having you to write (or atleast minimizes) comments of what the code does.
Tip #25
public async Task<List<Person>> GetPersonProfileByIdAsync(long personId)
{
//do something
}
This enable developers to easily identify synchornous vs asynchronous methods by just looking at the method itself.
Tip #26
Do prefix interfaces with the capital letter I
public interface IPersonManager
{
//...
}
This is to easily distinguish between an interface and classes. In fact, it's a well known standard for defining interfaces.
Tip #27
private readonly ILogger<ClassName> _logger;
private long _rowsAffected;
private IEnumerable<Persons> _people;
This is to easily differentiate between local and global identifiers/variables.
Tip #28
private static string _externalIdType;
private readonly ILogger<PersonManager> _logger;
private int _age;
This is just a generally accepted practice that prevents the need to hunt for variable declarations.
Tip #29
public class SomeClass
{
private void SomePublicMethodA()
{
}
// rest of your public methods here
private void SomePrivateMethodA()
{
}
private void SomePrivateMethodB()
{
}
}
Why? same reason for Tip #28.
Tip #30
#region Private Members
private void SomePrivateMethodA()
{
}
private void SomePrivateMethodB()
{
}
#endregion
The preceding code is a code smell which could potentially make your code grow without you realizing it. I admit that I have used this feature many times to collapse the code within a class. However, I realize that hiding code into regions won't give you any value aside from maximizing your visual view when the region is collapsed. If you are working with a team of developers on a project, chances are, other developers will append their code in there until the code get's bigger and bigger over time. As a good practice, it's always recommended to keep your classes small as possible.
If you have tons of private methods within a class, you could split them into a separate class instead.
Tip #30
private readonly CreateQuestionDefinitionRequestDto _requestDto;
It would be too much to name a variable "createQuestionDefinitionRequestDto" when you know that the variable/parameter is a request object. The same thing applies for FTP, UI, IO, etc. It's perfectly fine to use abbreviation for as long as they're generally known, otherwise it would be counter productive not to do so.
Tip #31
public PersonManager person_Manager;
private long rows_Affected;
private DateTime row_updated_date_time;
The reason being is that C# isn't postgres. Seriously, it's to be consistent with the Microsost .NET Framework convention and makes your code more natural to read. It can also avoid "underline stress" or inability to see underline.
Tip #32
public static const string EXTERNALIDTYPE = "ABC";
public static const string ENVIRONMENT_VARIABLE_NAME = "TEST";
They just grab too much attention.
Tip #33
int iCounter;
string strName;
string spCreateUsers;
OrderingService svcOrdering;
Visual Studio code editor already provides helpful tooltips to determine object types. In general, you want to avoid type indicators in the identifier.
Tip #34
The following is an example for defining an enum:
public enum BeerType
{
Lager,
Ale,
Ipa,
Porter,
Pilsner
}
Again, this is to be consistent with the Microsoft .NET framework and avoids type indicators in the identifier.
Tip #35
public class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
can be written in the following way using record:
public record Person(string FirstName, string LastName);
Using record types will automatically generates the boilerplate code for you and keeping your code concise. Records will be really useful for defining DTOs, Commands or any object that carries immutable data around. For more information about this feature, see: Record Types