Introduction: Two Sides of the Same Coin
\n\nImagine you are building a house. You can take ready-made blocks (classes, objects, inheritance) and assemble a complex structure from them — this is object-oriented programming (OOP). Or you can think like a mathematician: "I take data, apply a pure function to it, and get a new result without changing anything" — this is functional programming.
\n\nIn C#, both paradigms coexist in an amazing way. And if you write in this language, sooner or later you will ask yourself: "What if I'm doing this wrong? Maybe I should use more LINQ and fewer classes?"
\n\nIn this article, we won't argue about which is better. We will figure out how the functional approach differs from OOP in C#, when each one really helps, and when it hinders. And yes, there will be a lot of code. Let's go!
\n\n1. OOP in C#: The Classic That Works
\n\nObject-oriented programming is about state and behavior. You have a class that stores data (fields, properties) and methods that modify that data.
Example: an application for managing orders in an online store.
\n\npublic class Order\n{\n public int Id { get; set; }\n public List<string> Items { get; private set; }\n public decimal TotalPrice { get; private set; }\n\n public Order(int id)\n {\n Id = id;\n Items = new List<string>();\n TotalPrice = 0;\n }\n\n public void AddItem(string itemName, decimal price)\n {\n Items.Add(itemName);\n TotalPrice += price;\n }\n\n public void ApplyDiscount(decimal percent)\n {\n TotalPrice -= TotalPrice * (percent / 100);\n }\n}\n\nWhat do we see? Encapsulation: TotalPrice cannot be changed directly — only through methods. State of the object changes over time: we add items, apply discounts. This is pure OOP.
Pros of OOP in C#
\n- \n
- Understandable real-world model: Order, Customer, Product — everything is like in real life. \n
- Reusability through inheritance and interfaces. \n
- Complexity management by breaking it down into small objects. \n
Cons of OOP
\n- \n
- Side effects: one method can change state, and you easily lose control. \n
- Testing complexity: you need to create objects, mock dependencies. \n
- Redundancy: sometimes it's easier to write a function than to create a whole class. \n
2. Functional Approach in C#: LINQ and Beyond
\n\nFunctional programming is about pure functions (without side effects) and immutability of data. In C#, it is especially noticeable in LINQ, lambda expressions, and working with collections.
\n\nLet's solve the same problem — managing an order — but functionally.
\n\n// Immutable record\npublic record Order(int Id, List<string> Items, decimal TotalPrice);\n\npublic static class OrderFunctions\n{\n public static Order AddItem(Order order, string item, decimal price)\n {\n var newItems = order.Items.Append(item).ToList();\n return order with { Items = newItems, TotalPrice = order.TotalPrice + price };\n }\n\n public static Order ApplyDiscount(Order order, decimal percent)\n {\n return order with { TotalPrice = order.TotalPrice * (1 - percent / 100) };\n }\n}\n\n// Usage\nvar order = new Order(1, new List<string>(), 0);\norder = OrderFunctions.AddItem(order, "Laptop", 50000);\norder = OrderFunctions.ApplyDiscount(order, 10);\n\nNotice? We are not changing the original object. A new instance is created each time using with. This eliminates accidental changes.
And