DEV Community

mohamed Tayel
mohamed Tayel

Posted on

c# Clean Code: Best Practices for new, Operators, and using

In this article, we’ll explore some of the new syntax in C# and best practices that make your code clearer and more efficient. We'll cover each concept step-by-step, using detailed examples and breaking them down to ensure clarity. Let's get started!

1. New Syntax for Object Initialization

Traditionally, to create and initialize an object in C#, you would write:

Backpack myBackpack = new Backpack();
Enter fullscreen mode Exit fullscreen mode

Alternatively, you could use the var keyword:

var myBackpack = new Backpack();
Enter fullscreen mode Exit fullscreen mode

However, with the latest C# syntax, you can simplify this further:

Backpack myBackpack = new();
Enter fullscreen mode Exit fullscreen mode

Detailed Example

Old Syntax

Let's initialize a class called Backpack:

public class Backpack
{
    public string Color { get; set; }
    public int Capacity { get; set; }

    public Backpack(string color, int capacity)
    {
        Color = color;
        Capacity = capacity;
    }
}

// Usage
var myBackpack = new Backpack("Red", 30);
Enter fullscreen mode Exit fullscreen mode

Here, you’re specifying the type twice: once while declaring the variable (var myBackpack) and again when initializing (new Backpack). This can be redundant, especially in scenarios with complex types.

New Syntax

Backpack myBackpack = new("Red", 30);
Enter fullscreen mode Exit fullscreen mode

Breakdown

  • New Syntax: Reduces redundancy and keeps the declaration shorter.
  • Use Case: Ideal for readability and consistency, especially when the type is evident from the context.
  • Limitation: This works only when the type is specified explicitly (e.g., Backpack myBackpack = new()), not when using var.

Why It’s Useful

This new syntax makes your code more concise by eliminating repetitive type declarations. It’s particularly beneficial when dealing with types that are long or complex, such as collections or custom classes.

2. Short-Circuit Operators

Short-circuit operators (&& for AND, || for OR) improve performance by skipping unnecessary evaluations. Let’s explore this with an example.

Example: Avoiding Unnecessary Evaluations

Without Short-Circuiting

bool IsUserLoggedIn = false;
bool HasAdminAccess = false;

if (CheckIfUserLoggedIn() & CheckIfHasAdminAccess())
{
    Console.WriteLine("User has access.");
}

bool CheckIfUserLoggedIn()
{
    Console.WriteLine("Checking if user is logged in...");
    return IsUserLoggedIn;
}

bool CheckIfHasAdminAccess()
{
    Console.WriteLine("Checking admin access...");
    return HasAdminAccess;
}
Enter fullscreen mode Exit fullscreen mode

Breakdown

  • Output:
  Checking if user is logged in...
  Checking admin access...
Enter fullscreen mode Exit fullscreen mode

Here, both CheckIfUserLoggedIn() and CheckIfHasAdminAccess() are evaluated, even though the first condition returns false, making the second check unnecessary.

With Short-Circuiting

if (CheckIfUserLoggedIn() && CheckIfHasAdminAccess())
{
    Console.WriteLine("User has access.");
}
Enter fullscreen mode Exit fullscreen mode

Breakdown

  • Output:
  Checking if user is logged in...
Enter fullscreen mode Exit fullscreen mode

In this version, if CheckIfUserLoggedIn() returns false, CheckIfHasAdminAccess() is not evaluated, preventing potential errors and saving resources.

Why It’s Useful

  • Performance: Reduces unnecessary computations.
  • Error Prevention: Prevents exceptions in cases where the second condition might throw an error (e.g., a null check).
  • Readability: Makes the intent clearer—execute the second condition only when the first is true.

3. Simplify Resource Management with the using Statement

The using statement ensures that objects implementing the IDisposable interface are properly disposed of. This prevents memory leaks and manages resources more effectively.

Example: Managing File Resources

Without using

FileStream fileStream = null;
try
{
    fileStream = new FileStream("example.txt", FileMode.Open);
    // Perform file operations
}
finally
{
    if (fileStream != null)
    {
        fileStream.Dispose();
    }
}
Enter fullscreen mode Exit fullscreen mode

Breakdown

  • File Handling: Opens a file and performs operations.
  • Manual Disposal: You need to ensure that Dispose() is called in the finally block to release the file resource.

With using

using (FileStream fileStream = new FileStream("example.txt", FileMode.Open))
{
    // Perform file operations
}
Enter fullscreen mode Exit fullscreen mode

Breakdown

  • Automatic Disposal: The using statement ensures that Dispose() is called, even if an exception occurs.
  • Cleaner Code: No need to manually call Dispose(), reducing the chance of missing resource cleanup.

With New C# Syntax (Without Braces)

using var fileStream = new FileStream("example.txt", FileMode.Open);
// Perform file operations
Enter fullscreen mode Exit fullscreen mode

Breakdown

  • Simplified Syntax: You can now declare using without braces, which is ideal for single-line operations.
  • Automatic Disposal: As with the previous example, disposal is guaranteed when the variable goes out of scope.

Why It’s Useful

  • Simplicity: Eliminates the need for explicit finally blocks.
  • Safety: Ensures resources are released, preventing potential memory leaks.
  • Flexibility: Works well in scenarios where you need temporary resource handling.

4. Avoid Empty Catch Blocks

Handling exceptions properly is crucial for maintainability and debugging. An empty catch block hides errors, making it difficult to identify issues.

Example: Catch Block with Proper Error Logging

Problematic Code

try
{
    // Some operation that may throw an exception
}
catch
{
    // Empty catch block - hides exceptions
}
Enter fullscreen mode Exit fullscreen mode

Breakdown

  • Hides Errors: If an exception occurs, it is silently ignored, making it hard to trace bugs.

Better Code

try
{
    // Some operation that may throw an exception
}
catch (Exception ex)
{
    Console.WriteLine($"Error occurred: {ex.Message}");
}
Enter fullscreen mode Exit fullscreen mode

Breakdown

  • Logs Errors: Captures and logs the error message, providing visibility into issues.
  • Easier Debugging: Makes troubleshooting and debugging easier by showing what went wrong.

Why It’s Useful

  • Error Transparency: Ensures that errors are visible to developers.
  • Debugging: Provides context for resolving issues.
  • Best Practice: Always handle exceptions thoughtfully to avoid hiding potential problems.

Conclusion

By incorporating these new C# syntax improvements and best practices, you can write cleaner, more efficient, and maintainable code. Here's a quick recap:

  1. New Syntax for Object Initialization: Use Type variable = new(); for concise object creation.
  2. Short-Circuit Operators: Use && and || to skip unnecessary evaluations, enhancing performance.
  3. Using Statement for Resource Management: Simplify disposal of resources and prevent memory leaks.
  4. Avoid Empty Catch Blocks: Always log exceptions to maintain error visibility.

Implementing these best practices will help you develop code that’s not only easier to read but also performs better. Happy coding!

Top comments (0)