DEV Community

mohamed Tayel
mohamed Tayel

Posted on

C# Advanced: Creating Reusable Extension Methods

Extension methods are a powerful feature in C# that allow you to add new functionality to existing types without modifying their source code. This is particularly useful in scenarios where you don't control the codebase, such as when working with libraries installed via NuGet. You can create extension methods for almost everything: classes, records, structs, interfaces, and even delegates. However, it's important to follow best practices and only create extension methods for types that are out of your control or for reusable code that extends core functionality.

In this article, we'll explore how to create extension methods for a shared Order class within a large organization. This will help you understand how to extend functionality in a way that is maintainable and reusable across multiple projects.

Scenario Overview

Imagine you're working in a large organization where domain classes, such as Order, are shared across multiple projects. These classes are distributed via a private NuGet server, and you're tasked with adding functionality to the Order class. However, since the Order class is part of a shared domain library, you cannot modify it directly. Instead, you decide to create a reusable extension method that can be accessed by all projects depending on this domain library.

For simplicity, let's assume the Order class is defined as follows:

public class Order
{
    public int Id { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
    public DateTime OrderDate { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

This is a basic representation of an order, and it doesn't have much functionality. Now, let's extend this class to include a method that generates a report based on its data.

Creating the Extension Method

To extend the Order class, we need to create a static class that contains a static method. The first parameter of the method will use the this keyword to specify that it's extending the Order class. This makes it an extension method.

public static class OrderExtensions
{
    public static string GenerateReport(this Order order)
    {
        return $"Order Report: \nOrder ID: {order.Id}\nProduct: {order.ProductName}\nQuantity: {order.Quantity}\nOrder Date: {order.OrderDate:d}";
    }
}
Enter fullscreen mode Exit fullscreen mode

Here’s a breakdown of the key parts that make this an extension method:

  1. Static Class: The OrderExtensions class is static, meaning it cannot be instantiated.
  2. Static Method: The GenerateReport method is also static.
  3. this Keyword: The first parameter, this Order order, signifies that this method is an extension method for the Order class.

Now, you can use the GenerateReport method on any Order object, as if it were a built-in method of the class.

Example Usage

var order = new Order
{
    Id = 1,
    ProductName = "Laptop",
    Quantity = 5,
    OrderDate = DateTime.Now
};

string report = order.GenerateReport();
Console.WriteLine(report);
Enter fullscreen mode Exit fullscreen mode

Output:

Order Report: 
Order ID: 1
Product: Laptop
Quantity: 5
Order Date: 10/15/2024
Enter fullscreen mode Exit fullscreen mode

Overloading Extension Methods

You can also overload extension methods, just like regular methods. Let's say you want to add another version of the GenerateReport method that accepts an additional parameter, such as a custom footer for the report.

public static string GenerateReport(this Order order, string footer)
{
    return $"Order Report: \nOrder ID: {order.Id}\nProduct: {order.ProductName}\nQuantity: {order.Quantity}\nOrder Date: {order.OrderDate:d}\nFooter: {footer}";
}
Enter fullscreen mode Exit fullscreen mode

Now, you can call the method with or without the additional footer parameter.

string reportWithFooter = order.GenerateReport("Thank you for your order!");
Console.WriteLine(reportWithFooter);
Enter fullscreen mode Exit fullscreen mode

Method Resolution and Conflicts

What happens if a method with the same name already exists on the Order class? In C#, the compiler always prioritizes instance methods over extension methods. Let’s add a method directly to the Order class that conflicts with the GenerateReport extension method:

public class Order
{
    public int Id { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
    public DateTime OrderDate { get; set; }

    public string GenerateReport(string header)
    {
        return $"Order Header: {header}\nOrder ID: {Id}\nProduct: {ProductName}\nQuantity: {Quantity}\nOrder Date: {OrderDate:d}";
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, if you call the GenerateReport method on an Order object, the compiler will use the instance method instead of the extension method. However, you can still call the extension method by specifying named arguments, which allows you to resolve ambiguities:

string reportWithNamedArg = order.GenerateReport(footer: "Order Summary");
Enter fullscreen mode Exit fullscreen mode

This approach ensures you can still access the extension method if a conflict arises.

Packaging Your Extension Methods

In real-world scenarios, you would typically package your extension methods as a NuGet package, especially if the functionality is meant to be shared across multiple projects. Here’s how you can create and distribute your extension methods as a NuGet package:

Step 1: Create a New Class Library Project

  1. Open Visual Studio and create a new project by selecting File > New > Project.
  2. Choose the Class Library template, then click Next.
  3. Name the project something like OrderExtensionsLibrary, and specify a suitable location for your solution.
  4. Click Create to set up the project.

Step 2: Implement the Extension Methods

  1. Add your extension methods for the Order class to the class library. For example, create a class named OrderExtensions:
namespace OrderExtensionsLibrary
{
    public static class OrderExtensions
    {
        public static string GenerateReport(this Order order)
        {
            return $"Order Report: \nOrder ID: {order.Id}\nProduct: {order.ProductName}\nQuantity: {order.Quantity}\nOrder Date: {order.OrderDate:d}";
        }

        public static string GenerateReport(this Order order, string footer)
        {
            return $"Order Report: \nOrder ID: {order.Id}\nProduct: {order.ProductName}\nQuantity: {order.Quantity}\nOrder Date: {order.OrderDate:d}\nFooter: {footer}";
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Add a .nuspec File

To create a NuGet package, you need a .nuspec file that contains metadata about your package.

  1. Right-click on your project in Solution Explorer, then select Add > New Item.
  2. Choose XML File, name it OrderExtensionsLibrary.nuspec, and click Add.
  3. Define the contents of the .nuspec file as follows:
<?xml version="1.0"?>
<package>
  <metadata>
    <id>OrderExtensionsLibrary</id>
    <version>1.0.0</version>
    <authors>YourName</authors>
    <owners>YourOrganization</owners>
    <licenseUrl>https://example.com/license</licenseUrl>
    <projectUrl>https://example.com/project</projectUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Extension methods for the Order class used in multiple projects.</description>
    <tags>Order, Extensions, CSharp</tags>
  </metadata>
</package>
Enter fullscreen mode Exit fullscreen mode

Step 4: Build the NuGet Package

  1. Open the NuGet Package Manager Console in Visual Studio by selecting Tools > NuGet Package Manager > Package Manager Console.
  2. Run the following command to create the .nupkg file:
nuget pack OrderExtensionsLibrary.nuspec
Enter fullscreen mode Exit fullscreen mode

This will generate a .nupkg file in your project’s bin folder.

Step 5: Publish the Package to a NuGet Feed

You can either publish the package to a private NuGet server or to the public NuGet Gallery.

To publish to a private NuGet feed:

  1. Obtain the URL of your organization’s NuGet server.
  2. Run the following command to publish the package:
nuget push OrderExtensionsLibrary.1.0.0.nupkg -Source "https://your-nuget-server-url"
Enter fullscreen mode Exit fullscreen mode

To publish to the public NuGet Gallery:

  1. Create an account at nuget.org.
  2. Generate an API key from your account settings.
  3. Run the following command to publish the package:
nuget push OrderExtensionsLibrary.1.0.0.nupkg -ApiKey your-api-key -Source https://api.nuget.org/v3/index.json
Enter fullscreen mode Exit fullscreen mode

Step 6: Consume the NuGet Package in Other Projects

  1. Open a project that needs to use the extension methods.
  2. Right-click on the Dependencies node in Solution Explorer, then select Manage NuGet Packages.
  3. Search for OrderExtensionsLibrary and install it.
  4. Add a using directive in your code:
using OrderExtensionsLibrary;
Enter fullscreen mode Exit fullscreen mode

Now, you can use the GenerateReport method in any project that references the package.


Conclusion

Extension methods are an elegant solution when you need to add functionality to types that are out of your control, like shared domain classes. In this article, we demonstrated how to extend an Order class by creating a GenerateReport method, including overloading the method for additional customization. We also explored method resolution conflicts and explained how to package and distribute your extension methods as a NuGet package. By following best practices, such as keeping static classes focused on a single type, you can build robust and maintainable extension libraries for your applications.

Top comments (6)

Collapse
 
dotnetfullstackdev profile image
DotNet Full Stack Dev • Edited

@moh_moh701 Great explanation, and nice writing!
I have a query. Hope it's valid :)
As per our example, I have an instance method in an Order class
public string GenerateReport(string header)
{
}

If I want to add an extension method with the same signature
public static string GenerateReport(this Order order, string footer)
{
}

Is it allowed to create or not, if allows how can I call these methods one at a time seperately?

Collapse
 
moh_moh701 profile image
mohamed Tayel

by used naming parameter
var order = new Order
{
Id = 1,
ProductName = "Laptop",
Quantity = 5,
OrderDate = DateTime.Now
};

string report = order.GenerateReport();
Console.WriteLine(report);

string reportWithHeader = order.GenerateReport(header: "Order Summary");
Console.WriteLine(reportWithHeader);

string reportWithFooter = order.GenerateReport(footer:"Thank you for your order!");
Console.WriteLine(reportWithFooter);

Collapse
 
dotnetfullstackdev profile image
DotNet Full Stack Dev

@moh_moh701 Thanks for the response, I understood naming parameter calling!
As a little greedy developer, If I want to create the same method and the same naming parameters as below

public string GenerateReport(string header)
{
}
Enter fullscreen mode Exit fullscreen mode
public static string GenerateReport(this Order order, string header)
{
}
Enter fullscreen mode Exit fullscreen mode

is this way of implementation works, if so how can I call these methods individually..

Thread Thread
 
moh_moh701 profile image
mohamed Tayel

@dotnetfullstackdev
var order = new Order
{
Id = 1,
ProductName = "Laptop",
Quantity = 5,
OrderDate = DateTime.Now
};

string report = order.GenerateReport();
Console.WriteLine(report);

string reportWithHeader = order.GenerateReport(header: "Order Summary");
Console.WriteLine(reportWithHeader);

string reportWithhHeaderEx = OrderExtensions.GenerateReport(order,header: "Thank you for your order!");
Console.WriteLine(reportWithhHeaderEx);


public class Order
{
public int Id { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public DateTime OrderDate { get; set; }
//public string GenerateReport(string header)
//{
// return $"Order Header: {header}\nOrder ID: {Id}\nProduct: {ProductName}\nQuantity: {Quantity}\nOrder Date: {OrderDate:d}";
//}
public string GenerateReport(string header)
{
return "Instance Method called!";
}
}


public static class OrderExtensions
{
public static string GenerateReport(this Order order)
{
return $"Order Report: \nOrder ID: {order.Id}\nProduct: {order.ProductName}\nQuantity: {order.Quantity}\nOrder Date: {order.OrderDate:d}";
}
//public static string GenerateReport(this Order order, string footer)
//{
// return $"Order Report: \nOrder ID: {order.Id}\nProduct: {order.ProductName}\nQuantity: {order.Quantity}\nOrder Date: {order.OrderDate:d}\nFooter: {footer}";
//}

public static string GenerateReport(this Order order, string header)
{
    return "Extension Method called!";
}
Enter fullscreen mode Exit fullscreen mode

}

Thread Thread
 
dotnetfullstackdev profile image
DotNet Full Stack Dev

@moh_moh701 Excellent! The extension method always differs from the instance method as it defaults to this object (the actual class from which it is extended) parameter.
I learned a new thing for today, which added value to my day! Thanks for the detailed explanation.

Thread Thread
 
moh_moh701 profile image
mohamed Tayel

@dotnetfullstackdev you are welcome