DEV Community

mohamed Tayel
mohamed Tayel

Posted on

C# Advanced: Anonymous Types with LINQ

Introduction

Anonymous types are a valuable feature in C#, particularly when working with LINQ. They allow developers to create lightweight data structures on the fly, making complex queries more manageable. This article will guide you through anonymous types, their usage with LINQ, and practical examples to help you understand how to implement them effectively in your code.

1. What Are Anonymous Types?

Anonymous types in C# are unnamed data structures created using the new keyword. They are read-only and allow developers to group related data without defining a formal class.

Basic Example

var product = new { Name = "Laptop", Price = 1500 };
Console.WriteLine($"Product: {product.Name}, Price: {product.Price}");
Enter fullscreen mode Exit fullscreen mode
  • Properties: Anonymous types automatically generate properties based on the names you provide.
  • Read-Only: You can’t modify the properties once they are initialized.
  • Local Scope: They are primarily intended for temporary use within a method.

2. Why Are Anonymous Types Used with LINQ?

LINQ makes it easy to filter, project, and transform collections. Anonymous types complement LINQ by allowing developers to quickly create new structures for query results, avoiding the need to define extra classes.

3. Building a Real-World Example: Order Management System

Let’s assume we’re building an order management system where each order contains multiple items. We’ll use LINQ and anonymous types to generate order summaries.

Step 1: Setting Up Data Structures

First, create basic classes for Order and Item:

public class Order
{
    public string OrderNumber { get; set; }
    public List<Item> Items { get; set; }
}

public class Item
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Sample Data

Let’s populate some sample data for demonstration:

var orders = new List<Order>
{
    new Order 
    { 
        OrderNumber = "001", 
        Items = new List<Item> 
        { 
            new Item { Name = "Laptop", Price = 1000 }, 
            new Item { Name = "Mouse", Price = 50 } 
        } 
    },
    new Order 
    { 
        OrderNumber = "002", 
        Items = new List<Item> 
        { 
            new Item { Name = "Phone", Price = 800 }, 
            new Item { Name = "Charger", Price = 20 } 
        } 
    }
};
Enter fullscreen mode Exit fullscreen mode

Step 3: Projecting Order Summaries Using Anonymous Types

Now, let’s use LINQ to generate an order summary using anonymous types:

var orderSummaries = orders.Select(order => new
{
    Order = order.OrderNumber,
    ItemCount = order.Items.Count,
    TotalValue = order.Items.Sum(item => item.Price)
});
Enter fullscreen mode Exit fullscreen mode
  • Here, the Select method projects each Order into a new structure with three properties: Order, ItemCount, and TotalValue.
  • Anonymous Type is used to avoid creating a new class for order summaries.

Step 4: Displaying Results

foreach (var summary in orderSummaries)
{
    Console.WriteLine($"Order: {summary.Order}, Items: {summary.ItemCount}, Total: {summary.TotalValue}");
}
Enter fullscreen mode Exit fullscreen mode

Output:

Order: 001, Items: 2, Total: 1050
Order: 002, Items: 2, Total: 820
Enter fullscreen mode Exit fullscreen mode

4. Advanced LINQ Operations with Anonymous Types

Let’s explore some more complex operations, like sorting and filtering.

Example: Sorting by Total Value

var sortedSummaries = orderSummaries.OrderBy(summary => summary.TotalValue);

Console.WriteLine("Sorted Order Summaries:");
foreach (var summary in sortedSummaries)
{
    Console.WriteLine($"Order: {summary.Order}, Total: {summary.TotalValue}");
}
Enter fullscreen mode Exit fullscreen mode
  • Explanation: This example uses OrderBy to sort the anonymous types by the total value of each order.

Example: Filtering Orders with High Total Value

var highValueOrders = orderSummaries.Where(summary => summary.TotalValue > 900);

Console.WriteLine("High Value Orders:");
foreach (var summary in highValueOrders)
{
    Console.WriteLine($"Order: {summary.Order}, Total: {summary.TotalValue}");
}
Enter fullscreen mode Exit fullscreen mode
  • Explanation: The Where method filters the order summaries to only include those with a total value greater than 900.

5. Anonymous Types in UI Applications

In UI applications like WPF or WinForms, anonymous types can be bound to controls like data grids, providing a quick way to represent and display data.

Example: Binding to a WPF Data Grid

Here’s how you can bind the anonymous type data to a WPF DataGrid:

DataGrid dataGrid = new DataGrid();
dataGrid.ItemsSource = orderSummaries.ToList(); // Convert to List for binding
Enter fullscreen mode Exit fullscreen mode
  • The DataGrid automatically generates columns based on the anonymous type properties.
  • This demonstrates how anonymous types simplify data binding in UI components.

6. Modifying Anonymous Types

While anonymous types are read-only, you can create a new instance with modified values based on an existing one.

Example: Adding Shipment Status

var updatedSummaries = orderSummaries.Select(summary => new
{
    summary.Order,
    summary.ItemCount,
    summary.TotalValue,
    IsReadyForShipment = summary.TotalValue > 900
});
Enter fullscreen mode Exit fullscreen mode
  • This creates a new anonymous type with an additional property, IsReadyForShipment.

7. Limitations and Best Practices

Limitations

  • Scope: Anonymous types are limited to the method in which they are created.
  • Read-Only: They cannot be modified after initialization.
  • No Inheritance: They cannot be used in polymorphic scenarios.

Best Practices

  • Short-lived Use: Use anonymous types for short-lived data transformations, such as in LINQ queries.
  • Avoid Overuse: Don’t use them for complex data structures; define classes instead.
  • Local Context: Keep anonymous types within the same method to avoid complexity. Here are three assignments for each difficulty level, designed to strengthen the understanding of anonymous types and LINQ:

Assignment Level: Easy

Task: Create a list of products and use LINQ to project each product into an anonymous type containing only the product name and price.

Instructions

  1. Create a Product class:
   public class Product
   {
       public string Name { get; set; }
       public decimal Price { get; set; }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Define a list of products:
   var products = new List<Product>
   {
       new Product { Name = "Laptop", Price = 1500 },
       new Product { Name = "Smartphone", Price = 800 },
       new Product { Name = "Tablet", Price = 400 }
   };
Enter fullscreen mode Exit fullscreen mode
  1. Use LINQ to project this list into an anonymous type that contains only the Name and Price:
   var productSummaries = products.Select(p => new
   {
       p.Name,
       p.Price
   });

   foreach (var summary in productSummaries)
   {
       Console.WriteLine($"Product: {summary.Name}, Price: {summary.Price}");
   }
Enter fullscreen mode Exit fullscreen mode
  • Goal: Ensure you understand how to create and use anonymous types to simplify projections.

Assignment Level: Medium

Task: Given a list of orders, create an anonymous type that contains the order number, total item count, and average item price. Then, filter only those orders with an average item price greater than 50.

Instructions

  1. Create Order and Item classes:
   public class Order
   {
       public string OrderNumber { get; set; }
       public List<Item> Items { get; set; }
   }

   public class Item
   {
       public string Name { get; set; }
       public decimal Price { get; set; }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Define a list of orders:
   var orders = new List<Order>
   {
       new Order 
       { 
           OrderNumber = "001", 
           Items = new List<Item> 
           { 
               new Item { Name = "Laptop", Price = 1000 },
               new Item { Name = "Mouse", Price = 30 }
           }
       },
       new Order 
       { 
           OrderNumber = "002", 
           Items = new List<Item> 
           { 
               new Item { Name = "Phone", Price = 800 },
               new Item { Name = "Charger", Price = 20 }
           }
       }
   };
Enter fullscreen mode Exit fullscreen mode
  1. Use LINQ to create the anonymous type and filter results:
   var orderSummaries = orders.Select(order => new
   {
       OrderNumber = order.OrderNumber,
       TotalItems = order.Items.Count,
       AveragePrice = order.Items.Average(item => item.Price)
   })
   .Where(summary => summary.AveragePrice > 50);

   foreach (var summary in orderSummaries)
   {
       Console.WriteLine($"Order: {summary.OrderNumber}, Avg Price: {summary.AveragePrice}");
   }
Enter fullscreen mode Exit fullscreen mode
  • Goal: Learn to combine projections and filters using LINQ with anonymous types.

Assignment Level: Difficult

Task: Implement a library management system that tracks borrowed books and generates an anonymous type that summarizes each user’s borrowing activity, including the user’s name, total books borrowed, and the total price of borrowed books. Additionally, order the results by the total price of borrowed books in descending order.

Instructions

  1. Create User, Book, and BorrowRecord classes:
   public class User
   {
       public string Name { get; set; }
   }

   public class Book
   {
       public string Title { get; set; }
       public decimal Price { get; set; }
   }

   public class BorrowRecord
   {
       public User User { get; set; }
       public List<Book> Books { get; set; }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Define a list of borrow records:
   var borrowRecords = new List<BorrowRecord>
   {
       new BorrowRecord 
       { 
           User = new User { Name = "Alice" },
           Books = new List<Book>
           {
               new Book { Title = "C# Programming", Price = 40 },
               new Book { Title = "ASP.NET Core Guide", Price = 60 }
           }
       },
       new BorrowRecord 
       { 
           User = new User { Name = "Bob" },
           Books = new List<Book>
           {
               new Book { Title = "Data Structures", Price = 50 },
               new Book { Title = "LINQ in Action", Price = 45 }
           }
       }
   };
Enter fullscreen mode Exit fullscreen mode
  1. Use LINQ to create the anonymous type and sort the results:
   var userSummaries = borrowRecords.Select(record => new
   {
       UserName = record.User.Name,
       TotalBooks = record.Books.Count,
       TotalPrice = record.Books.Sum(book => book.Price)
   })
   .OrderByDescending(summary => summary.TotalPrice);

   foreach (var summary in userSummaries)
   {
       Console.WriteLine($"User: {summary.UserName}, Total Books: {summary.TotalBooks}, Total Price: {summary.TotalPrice}");
   }
Enter fullscreen mode Exit fullscreen mode

Conclusion

Anonymous types, when used effectively with LINQ, can make code more readable and maintainable. They offer a clean way to project complex data into simpler forms without defining additional classes. However, they are best suited for short-lived, local transformations and should not be used as a replacement for well-defined data structures.

Top comments (0)