TL;DR

We’ve all written classes that expose public data, a List<Order> here, a Dictionary<string, decimal> there. It’s tempting to just slap public on everything and move on. But here’s what that approach costs you: control, flexibility, and peace of mind.

When you expose raw data, you’re basically handing over the keys to your object’s internals. Anyone can mess with your collections, break your business rules, or put your object in an invalid state.

Before: Exposing Data

public class OrderManager
{
    public List<Order> Orders { get; set; } = new();
    public decimal TotalRevenue { get; set; }
    
    // Anyone can break our rules
}

// Consumers can do dangerous things
manager.Orders.Clear(); // Oops, lost all orders
manager.TotalRevenue = -500; // Invalid state

After: Exposing Behavior

public class OrderManager
{
    private readonly List<Order> _orders = new();
    private decimal _totalRevenue;
    
    public int OrderCount => _orders.Count;
    public decimal GetTotalRevenue() => _totalRevenue;
    public bool HasPendingOrders() => _orders.Any(o => o.Status == OrderStatus.Pending);
    
    public void AddOrder(Order order)
    {
        if (order == null) throw new ArgumentNullException(nameof(order));
        
        _orders.Add(order);
        _totalRevenue += order.Amount;
    }
    
    public IReadOnlyList<Order> GetOrdersByStatus(OrderStatus status) 
        => _orders.Where(o => o.Status == status).ToList();
}

Why This Works Better

Protected invariants: Your object controls how its state changes. No more surprise bugs from external code messing with your data.

Clear contracts: Methods like HasPendingOrders() tell consumers exactly what they can do. No guessing what properties mean or how to use them safely.

Future flexibility: Need to change how you store orders internally? Switch from a List to a database? Your public interface stays the same.

Think of it like a coffee machine, you press buttons to get coffee, you don’t open it up and fiddle with the heating element. Same principle: expose what the object does, not how it stores things internally.

The Mental Model

Ask yourself: “What does this object help people accomplish?” Then design methods around those goals, not around your internal data structures.

Your classes become more reliable, your APIs become clearer, and your future self won’t curse your name when requirements change. Expose behavior, hide data, it’s that simple.

Frequently Asked Questions

What does it mean to expose behavior instead of data in C#?

Exposing behavior means providing methods that perform actions or queries, rather than exposing internal fields or collections directly. This approach lets the class control how its state changes and enforces business rules. It results in safer, more maintainable code.

Why is exposing public data fields or collections risky?

How does exposing behavior improve code flexibility?

By exposing behavior, you can change the internal implementation (such as switching from a list to a database) without affecting consumers of your class. The public interface remains stable, making future changes easier and safer.

What are the benefits of using methods like AddOrder or GetOrdersByStatus?

Methods like AddOrder and GetOrdersByStatus encapsulate business logic and validation, ensuring that only valid operations are performed. This protects the object’s invariants and provides a clear, intention-revealing API for consumers.

How does exposing behavior support information hiding and encapsulation?

Exposing behavior hides the internal data structures and implementation details from consumers. This enforces encapsulation, allowing you to protect invariants, enforce rules, and change internals without breaking external code.
See other c-sharp posts