Extension Properties and Indexers in C#

Extension Properties and Indexers in C#

In this article, I will discuss Extension Properties and Indexers in C# with Examples. Please read our previous article discussing Field Keyword in C# with Examples. With C# 13, extension methods have been enhanced to allow the extension of properties and indexers in addition to instance methods. This powerful feature makes extending existing types easier and more flexible, allowing developers to add functionality to classes, structs, and collections in a way that was not possible in earlier versions of C#.

What Are Extension Methods?

An extension method allows you to add methods to an existing type without modifying its original source code. These methods are defined in a static class and are treated as if they are part of the type they extend. This allows you to add functionality to types, including those you do not have access to modify directly (e.g., third-party libraries).

In C# 13, extension methods can now be used to extend properties and indexers in addition to the instance methods that were supported in earlier versions. The following is the Basic Syntax of an Extension Method:

public static class MyExtensions
{
    public static void MyExtensionMethod(this MyClass obj)
    {
        // Extension method logic
    }
}

The “this” keyword before the first parameter in the extension method allows it to extend the type of that parameter.

Before C# 13: Limitations of Extension Methods

Before C# 13, extension methods could only be applied to instance methods. You could not use extension methods to extend properties or indexers. This limited the flexibility of extension methods, especially when you wanted to add functionality for properties or indexers without modifying the original type.

What’s New in C# 13?

With C# 13, extension methods can now extend:

  • Properties
  • Indexers

This allows you to add custom logic to getters and setters of properties and to add custom behavior to indexers on collections or other types.

Extension Methods for Properties in C#

You can now create extension methods that get or set the values of properties on existing types. This allows enhanced functionality, such as validation or additional logging during property access. Extension methods for properties work similarly to those for methods. The “this” keyword is used to specify that the method extends the property on a particular type.

Extending the BankAccount class with custom logic for property extension. In a banking system, you may want to extend the Balance property of a BankAccount class to include custom formatting, validation, or other functionality, such as checking if the balance is negative.

namespace CSharp13NewFeatures
{
    // BankAccount class with a Balance property
    public class BankAccount
    {
        public decimal Balance { get; set; }

        public BankAccount(decimal initialBalance)
        {
            Balance = initialBalance;
        }
    }

    // Extension methods for BankAccount properties
    public static class BankAccountExtensions
    {
        // Extending the Balance property to apply custom formatting
        public static string GetFormattedBalance(this BankAccount account)
        {
            return $"${account.Balance:F2}";  // Formats the balance as currency (e.g., $100.00)
        }

        // Extending the Balance property to check if the balance is negative
        public static bool IsOverdrawn(this BankAccount account)
        {
            return account.Balance < 0;  // Checks if the balance is negative (overdrawn)
        }
    }

    class Program
    {
        static void Main()
        {
            // Create a new bank account with an initial balance
            var account = new BankAccount(1000m);

            // Use the extension method to get a formatted balance
            Console.WriteLine($"Account Balance: {account.GetFormattedBalance()}");

            // Withdraw some amount and check if the account is overdrawn
            account.Balance -= 1100m;  // Withdraw more than the balance
            Console.WriteLine($"Account Balance after withdrawal: {account.GetFormattedBalance()}");

            // Check if the account is overdrawn
            if (account.IsOverdrawn())
            {
                Console.WriteLine("Warning: Your account is overdrawn.");
            }
            else
            {
                Console.WriteLine("Your account is in good standing.");
            }
        }
    }
}
Code Explanation:
  • BankAccount Class: Represents a basic bank account with a Balance property.
  • Extension Methods: The GetFormattedBalance method formats the Balance property as currency with two decimal places. The IsOverdrawn method Checks if the Balance is negative (indicating the account is overdrawn).
Output:

Extension Methods for Properties in C#

Extension Methods for Indexers in C#

You can create extension methods for indexers, making it easier to add custom behavior when accessing elements of collections or arrays. You can extend indexers by adding custom behavior when accessing items by index.

Extending the indexer to manage product quantities in an inventory system. In an inventory management system, products may be represented as a collection, and we can use indexers to manage the stock of each product. We will create extension methods to add additional functionality, such as applying a discount to the quantity or resetting the stock.

namespace CSharp13NewFeatures
{
    // Product class with an indexer to access stock by index
    public class Inventory
    {
        private Dictionary<int, int> _productStock = new Dictionary<int, int>();  // Product ID to stock quantity mapping

        public int this[int productId]
        {
            get
            {
                return _productStock.ContainsKey(productId) ? _productStock[productId] : 0;
            }
            set
            {
                if (_productStock.ContainsKey(productId))
                {
                    _productStock[productId] = value;  // Update stock for the existing product
                }
                else
                {
                    _productStock.Add(productId, value);  // Add a new product with the given stock value
                }
            }
        }

        // Add products to the inventory
        public void AddProduct(int productId, int initialStock)
        {
            _productStock[productId] = initialStock;
        }
    }

    // Extension methods for Inventory class to extend the indexer
    public static class InventoryExtensions
    {
        // Extending the indexer to apply a discount on the quantity of a product
        public static void ApplyDiscount(this Inventory inventory, int productId, int discountPercentage)
        {
            int stock = inventory[productId];
            int discountedStock = stock - (stock * discountPercentage / 100);
            inventory[productId] = discountedStock;
            Console.WriteLine($"Product {productId}: Discount applied. New stock: {discountedStock}");
        }

        // Extending the indexer to reset the stock to 0 for a specific product
        public static void ResetStock(this Inventory inventory, int productId)
        {
            inventory[productId] = 0;
            Console.WriteLine($"Product {productId}: Stock has been reset to 0.");
        }
    }

    public class Program
    {
        static void Main()
        {
            // Create an inventory and add some products with initial stock
            var inventory = new Inventory();
            inventory.AddProduct(101, 100);  // Product ID 101 with 100 units
            inventory.AddProduct(102, 50);   // Product ID 102 with 50 units

            // Display current stock for each product
            Console.WriteLine($"Product 101 Stock: {inventory[101]}");
            Console.WriteLine($"Product 102 Stock: {inventory[102]}");

            // Use the extension method to apply a discount (e.g., 10% off)
            inventory.ApplyDiscount(101, 10);  // Apply 10% discount to product 101

            // Reset the stock for product 102
            inventory.ResetStock(102);  // Reset product 102's stock

            // Display the updated stock
            Console.WriteLine($"Product 101 Stock: {inventory[101]}");
            Console.WriteLine($"Product 102 Stock: {inventory[102]}");
        }
    }
}
Code Explanation:
  • Inventory Class: Represents an inventory of products with an indexer to manage the stock by product ID.
  • Extension Methods: The ApplyDiscount method applies a discount (percentage) to the stock of a specific product, reducing its quantity accordingly. The ResetStock method resets the stock of a specific product to zero.
Output:

Extension Methods for Indexers in C#

Real-Time Use Case: E-commerce System

In an e-commerce platform, you may need to manage products, and each product can have properties (like Name and Price) and stock levels represented as indexers. With C# 13’s new ability to extend properties and indexers, you can:

  • Add extra functionality to properties like automatically formatting product names (e.g., converting them to uppercase).
  • Modify stock levels through indexers with additional logic, such as validating stock or adjusting quantities based on promotions or discounts.

This feature is especially useful for e-commerce systems, where you need to extend existing models (such as products, orders, and inventory) without modifying the original source code. In the example below, we will extend the Product class to include properties and indexers. We will add methods to:

  • Set a property to uppercase.
  • Modify stock levels using indexers.

So, please modify the Program class as follows:

namespace CSharp13NewFeatures
{
    // Define a class with a property and an indexer
    public class Product
    {
        public string Name { get; set; } = null!;
        private int[] _stock = new int[10]; // Representing stock for the product

        // Indexer to access and modify stock by index
        public int this[int index]
        {
            get { return _stock[index]; }
            set { _stock[index] = value; }
        }
    }

    // Extension methods for properties and indexers
    public static class ProductExtensions
    {
        // Extending the 'Name' property to set it to uppercase
        public static void SetUpperCaseName(this Product product)
        {
            product.Name = product.Name.ToUpper();  // Convert the name to uppercase
            Console.WriteLine($"Product Name set to: {product.Name}");
        }

        // Extending the indexer to add a quantity to the stock at a specific index
        public static void AddToStock(this Product product, int index, int quantity)
        {
            product[index] += quantity;  // Add to stock at the given index
            Console.WriteLine($"Stock at index {index} updated to: {product[index]}");
        }
    }

    public class Program
    {
        static void Main()
        {
            // Create a Product instance
            Product product = new Product { Name = "Laptop" };

            // Using the extension method to convert the product name to uppercase
            product.SetUpperCaseName();

            // Use the extension method to add to the stock at index 0
            product.AddToStock(0, 50);

            // Use the indexer to get and set stock manually
            product[1] = 20;  // Set stock at index 1
            Console.WriteLine($"Stock at index 1: {product[1]}");

            // Display the updated stock at index 0
            Console.WriteLine($"Stock at index 0 after adding: {product[0]}");
        }
    }
}
Explanation of the Code:
  • Product Class: The Product class has a Name property and an indexer that stores the stock in an array (_stock). The indexer allows accessing or modifying the stock at any index.
  • Extension Methods: The SetUpperCaseName extension method converts the Name property to uppercase. This method adds additional functionality to the Name property that wasn’t initially available. The AddToStock extension method allows adding a specified quantity to the stock at a specific index.
  • Usage: The SetUpperCaseName method converts the Name property to uppercase. The AddToStock method adds 50 units to the stock at index 0. The indexer is used to set and get stock levels.
Output:

Extension Properties and Indexers in C# with Examples

The C# 13 feature of extension methods for properties and indexers significantly enhances the flexibility and power of extension methods. You can now:

  • Extend properties to include custom logic (like formatting or validation).
  • Extend indexers to modify data access patterns, such as adjusting stock levels in collections or arrays.

These enhancements allow you to add functionality to existing types in a clean, modular way, making your codebase more maintainable and scalable, especially when working with third-party libraries or predefined classes.

In the next article, I will discuss the Overload Resolution Priority Attribute in C# with Examples. In this article, I explain Extension Properties and Indexers in C# with Examples. I would like your feedback. Please post your feedback, questions, or comments about this article.

Leave a Reply

Your email address will not be published. Required fields are marked *