Introduction
Welcome to the world of C#! Whether you’re a seasoned developer or just starting your programming journey, the power and versatility of C# can elevate your coding experience. In this article, we’ll explore a curated collection of tips and tricks to enhance your proficiency in C#. From optimizing code performance to leveraging advanced language features, these tips are designed to empower you in writing cleaner, more efficient, and robust C# code. Let’s dive into the intricacies of C# and uncover some valuable insights that will undoubtedly sharpen your programming skills.
1. Choosing between Any and Count
- Use
Any
when you are interested in whether at least one element meets a certain condition, and you don't need to know the count.
- Use
Count
when you need to know the number of elements that meet a specific condition.
Any
:
- Any is used to determine whether any elements in a sequence satisfy a specific condition.
- It returns a Boolean indicating whether there are any elements in the collection.
- It stops iterating through the collection as soon as it finds the first matching element.
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
bool anyGreaterThanThree = numbers.Any(x => x > 3);
Count
:
- Count is used to get the number of elements in a sequence that satisfy a specific condition.
- It counts all the elements in the collection that meet the specified criteria.
- It iterates through the entire collection to count all matching elements.
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
int countGreaterThanThree = numbers.Count(x => x > 3);
2. Avoid using ToUpper / ToLower when comparing strings
When comparing strings is valid, especially when you want to perform case-insensitive comparisons. Using ToUpper()
or ToLower()
can be less efficient because it involves creating new string objects.
The second approach is, using string.Equals
with StringComparison.OrdinalIgnoreCase
, is a more efficient and idiomatic way to achieve case-insensitive string comparison in .NET. This approach avoids creating additional string objects and provides better performance.
public bool AreStringsEqual(string first, string second)
{
// slow
return first.ToUpper() == second.ToUpper();
}
public bool AreStringsEqual(string first, string second)
{
// fast
return string.Equals(first, second, StringComparison. OrdinalIgnoreCase);
}
3. Multiple OrderBy() vs ThenBy()
OrderBy
:
- The
OrderBy
method is used to sort the elements of a sequence in ascending order based on a specified key.
- It returns a new sequence in which the elements are sorted based on the specified key.
- If you want to sort a collection based on multiple criteria, you should use OrderBy for the primary sorting criterion.
List<Person> people = GetPeople();
var sortedPeople = people.OrderBy(person => person.LastName);
ThenBy
:
- The
ThenBy
method is used for secondary sorting. It is applied to the result of OrderBy and further refines the order of elements based on another key.
- It is used when you have already sorted the collection using
OrderBy
, and you want to add additional sorting criteria.
List<Person> people = GetPeople();
var sortedPeople = people
.OrderBy(person => person.LastName)
.ThenBy(person => person.FirstName);
Example with Multiple OrderBy
:
You can use multiple OrderBy
clauses to achieve the same result as OrderBy
and ThenBy
. However, using ThenBy
is generally more readable and can be more efficient.
List<Person> people = GetPeople();
var sortedPeople = people
.OrderBy(person => person.LastName)
.OrderBy(person => person.FirstName); // This is less readable and can be less efficient
When to use each:
- Use
OrderBy
for the primary sorting criterion.
- Use
ThenBy
for secondary, tertiary, and subsequent sorting criteria.
- Use multiple
OrderBy
clauses only when you want to replace the existing order with a new one.
Summary:
- OrderBy: Use for the primary sorting criterion.
- ThenBy: Use for secondary and subsequent sorting criteria.
- Multiple OrderBy: Less common; use only when you want to replace the existing order.
4. Strings should not be concatenated using ‘+’ in a loop
Strings are immutable, which means that once a string object is created, it cannot be modified. When you concatenate strings using the ‘+’ 𝗼𝗽𝗲𝗿𝗮𝘁𝗼𝗿 in a loop, a new string object is created at each iteration, and the previous objects are discarded. This can lead to performance issues, especially when dealing with large strings or a large number of iterations.
A more efficient approach to string concatenation in C# is to use the 𝗦𝘁𝗿𝗶𝗻𝗴𝗕𝘂𝗶𝗹𝗱𝗲𝗿 𝗰𝗹𝗮𝘀𝘀, which is designed for efficiently building strings in a loop. StringBuilder allows you to append strings without creating new objects each time, which leads to better performance.
𝗦𝘁𝗿𝗶𝗻𝗴𝗕𝘂𝗶𝗹𝗱𝗲𝗿 is more useful when dealing with large strings or a large number of iterations and when we have an unknown amount of strings.
By using 𝗦𝘁𝗿𝗶𝗻𝗴𝗕𝘂𝗶𝗹𝗱𝗲𝗿, you can significantly reduce memory allocations and improve the performance of your code when you need to concatenate strings in a loop. It is a best practice to use StringBuilder when working with dynamic string building operations.
// + operator
string result = string.Empty;
foreach (var str in arrayOfStrings)
{
result += str;
}
// builder
StringBuilder builder = new StringBuilder();
foreach (var str in arrayOfStrings)
{
builder.Append(str);
}
string result = builder.ToString();
5. AsNoTracking
In Entity Framework Core, the AsNoTracking
method is often used to improve performance when retrieving entities from the database. The AsNoTracking
method informs EF Core that the entities being queried don't need to be tracked for changes, which can result in faster queries and reduced memory consumption. Here's a brief explanation of the improvement:
public List<User> GetUsers()
{
// Slow - Tracking entities for changes
return db.Users.ToList();
}
public List<User> GetUsers()
{
// Fast - Not tracking entities for changes
return db.Users.AsNoTracking().ToList();
}
Adding AsNoTracking
indicates to EF Core that the entities retrieved should not be tracked. This can significantly improve the performance of the query, especially when dealing with large result sets, as it avoids the overhead of change tracking.
6. Class vs Record
When using Data Transfer Objects (DTOs) in C#, the choice between using classes or records depends on your specific requirements and the characteristics of the data you’re transferring. Both classes and records can be used for defining DTOs, but each has its own strengths and use cases. Let’s explore the considerations for using classes and records for DTOs.
Using a Class for DTO:
public class ProductDTO
{
public string Name { get; set; }
public double Price { get; set; }
public int StockQuantity { get; set; }
}
Pros:
- Full control over mutability: You can use properties with getters and setters, giving you the flexibility to make properties mutable if needed.
- Extensibility: You can add methods, implement interfaces, and provide custom behavior as necessary.
Cons:
- Boilerplate code: More lines of code are required for property declarations, getters, setters, and potentially other methods.
Using a Record for DTO:
public record ProductDTO(
string Name,
double Price,
int StockQuantity);
Pros:
- Concise syntax: Records provide a concise and expressive way to define immutable DTOs with minimal boilerplate code.
- Automatic value-based equality: Records automatically generate
Equals
, GetHashCode
, and ToString
methods based on the property values.
Cons:
- Limited mutability: Records are inherently designed for immutability, and their properties are implicitly read-only. While you can use init in the property declaration to set values during initialization, the properties remain read-only once set.
Choosing between Class and Record for DTO:
Use a Class when:
- You need full control over mutability.
- You want to provide custom behavior through methods.
- You need to implement interfaces or provide custom serialization logic.
Use a Record when:
- You are modeling immutable data transfer objects.
- You want a concise syntax for defining DTOs.
- Value-based equality is beneficial for your use case.𝗨𝘀𝗲 ‘𝗠𝗶𝗻𝗕𝘆’ 𝗼𝗿
7. MinBy() and MaxBy() instead of ordering and taking ‘First’ or ‘Last’
In LINQ, there isn’t a direct MinBy
or MaxBy method, but you can achieve similar functionality by using the OrderBy
or OrderByDescending
methods along with the First
or Last
method. However, if you want to avoid sorting the entire collection and only find the minimum or maximum element based on a specific criteria, you can create extension methods for MinBy
and MaxBy
.
The MinBy
and MaxBy
extension methods offer advantages over sorting the entire collection when you need to find the minimum or maximum element based on a specific key. These advantages include increased efficiency, optimized key comparison, lazy evaluation for better performance, adaptability to different collections, customization through key selector functions, and avoidance of sorting overhead.
List<int> numbers = new List<int> { 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5 };
// Traditional Approach using OrderBy and First
var orderedNumbers = numbers.OrderBy(x => x);
int minNumberTraditional = orderedNumbers.First();
int maxNumberTraditional = orderedNumbers.Last();
// Using MinBy and MaxBy extension methods
int minNumber = numbers.MinBy(x => x);
int maxNumber = numbers.MaxBy(x => x);