BlackWasp
C# Programming
.NET 1.1+

C# Nested Classes

When one class is entirely dependent upon another, you may decide to implement it using a nested class. Nested classes are declared within the scope of an existing class and receive special benefits when they access their parent class' members.

What are Nested Classes?

A nested class is a special type of class that can be created using C#. Normally classes are declared within a namespace, or in the default namespace if one is not specified. A nested class is defined within the code block of another class, which itself may be a nested class to permit multiple nesting levels.

A nested class generally behaves is the same way as any other class. It can be instantiated separately to its parent class and is not instantiated automatically by its parent. However, there are some important differences. One key difference concerns the scope of the class. Normally classes can be either public or internal. A nested class may be declared as private, meaning that the class can only be seen by its parent class and other nested classes within that parent. A nested class may also be declared as protected, allowing it to also be accessed by classes that derive from its parent type. This may be extended to protected internal to allow other classes in the same assembly to access it.

Nested classes are given special access to instances of their parent classes. When an object of a parent class is passed to a method of a nested class, the method is permitted to access the private and protected members of the object. This makes nested classes ideal where a special relationship exists with the parent class that requires access to private members, particularly when the nested class is not required anywhere else and is never used in a standalone capability.

Example Nested Classes

We will start with a simple example of a nested class. In this case, the outer class will describe a sales order in an order-processing system. In this rather contrived design, there is no requirement for an order line to ever be created without an order. Furthermore, there is no need for an order line to be used externally to an order. In this case, it is useful to create an Order class with a nested, private OrderLine class.

NB: The following code uses .NET 3.0 syntax for brevity. For earlier versions of the .NET framework, you will need to expand the property declarations and possibly remove the use of generics.

class Order
{
    private List<OrderLine> _orderLines = new List<OrderLine>();

    public void AddOrderLine(string product, int quantity, double price)
    {
        OrderLine line = new OrderLine();
        line.ProductName = product;
        line.Quantity = quantity;
        line.Price = price;
        _orderLines.Add(line);
    }

    public double OrderTotal()
    {
        double total = 0;
        foreach (OrderLine line in _orderLines)
        {
            total += line.OrderLineTotal();
        }
        return total;
    }

    // Nested class
    private class OrderLine
    {
        public string ProductName { get; set; }
        public int Quantity { get; set; }
        public double Price { get; set; }

        public double OrderLineTotal()
        {
            return Price * Quantity;
        }
    }
}

The code above shows the simplified Order and nested OrderLine classes. The Order class has two public members. The first allows an item to be added to a private collection of order lines. The product name, price and quantity are passed to this method, which builds an OrderLine object and adds it to the list. The second method loops through each item in the collection and calculates the total value for the order.

The nested OrderLine class is used to represent order lines. It includes properties for the product name, quantity and price and a single method that calculates the total price for the order line. As the class is marked as private, it is invisible to all classes except its parent and any other nested classes that we may later create within Order. We could change the access modifier for the nested class to protected if we wished it to be visible to classes that inherit from Order.

We can test the order mechanism with the following code. This creates an order with two order lines and outputs the total value to the console:

Order order = new Order();
order.AddOrderLine("Cheese", 5, 1.99);
order.AddOrderLine("Oranges", 12, 0.35);
Console.WriteLine(order.OrderTotal());  // Outputs "14.15"

Increasing Nested Class Visibility

As our ordering system evolves, we may decide that it would be useful to permit other classes access to the nested OrderLine class. We could then modify the AddOrderLine method to accept an OrderLine object rather than its constituent parts. We can do this by changing the access modifier of the nested class to internal, protected internal or public, depending upon the requirements. The following code shows a new version of the code:

class Order
{
    private List<OrderLine> _orderLines = new List<OrderLine>();

    public void AddOrderLine(OrderLine line)
    {
        _orderLines.Add(line);
    }

    public double OrderTotal()
    {
        double total = 0;
        foreach (OrderLine line in _orderLines)
        {
            total += line.OrderLineTotal();
        }
        return total;
    }

    // Nested class
    internal class OrderLine
    {
        public string ProductName { get; set; }
        public int Quantity { get; set; }
        public double Price { get; set; }

        public double OrderLineTotal()
        {
            return Price * Quantity;
        }
    }
}

With the new versions of the classes, the code that creates an order will also be different. Rather than passing the product details as separate variables, the calling class will instantiate order lines and pass these to the AddOrderLine method. When instantiating a nested class, the name of the class must be qualified by prefixing it with the parent class' name, as in the following, updated example. Note that the order lines are created as Order.OrderLine objects.

Order order = new Order();

Order.OrderLine line1 = new Order.OrderLine();
line1.ProductName = "Cheese";
line1.Quantity = 5;
line1.Price = 1.99;
order.AddOrderLine(line1);

Order.OrderLine line2 = new Order.OrderLine();
line2.ProductName = "Oranges";
line2.Quantity = 12;
line2.Price = 0.35;
order.AddOrderLine(line2);

Console.WriteLine(order.OrderTotal());

Accessing Private Members of the Parent Class

The final example will demonstrate the ability for nested classes to access private members of their parent classes. In this example, we have modified the order to include a discount multiplier. The multiplier is held as a private variable that may have been initialised using a constructor or a property. In this case the value is fixed at 0.85. Every order line value will be multiplied by this amount to give a 15% discount.

To allow the order lines to access the discount multiplier, the OrderLineTotal method has been updated to include a parameter. This parameter accepts an Order object. The method retrieves the private discount multiplier from the Order and includes it in the calculation, rounding the result to two decimal places on a line-by-line basis. If the OrderLine class was not nested within Order, this would not be possible.

class Order
{
    private List<OrderLine> _orderLines = new List<OrderLine>();
    private double DiscountMultiplier = 0.85;

    public void AddOrderLine(OrderLine line)
    {
        _orderLines.Add(line);
    }

    public double OrderTotal()
    {
        double total = 0;
        foreach (OrderLine line in _orderLines)
        {
            total += line.OrderLineTotal(this);
        }
        return total;
    }

    // Nested class
    internal class OrderLine
    {
        public string ProductName { get; set; }
        public int Quantity { get; set; }
        public double Price { get; set; }

        public double OrderLineTotal(Order order)
        {
            return Math.Round(Price * Quantity * order.DiscountMultiplier, 2);
        }
    }
}

NB: Although nested classes can access the private members of their parent class, the reverse is not true. Private members of nested classes are invisible to their parent class.

Link to this Page21 March 2009
TwitterTwitter RSS Feed RSS