C# -

Encapsulation

Encapsulation is one of the four fundamental principles of object-oriented programming (OOP) in C#. It is the process of bundling the data (fields) and the methods (functions) that operate on the data into a single unit, typically a class, and restricting direct access to some of the object's components. This tutorial covers the basics of encapsulation, different types of encapsulation, access modifiers, and best practices.


1. Understanding Encapsulation

Encapsulation allows a class to hide its data members from other classes and only expose the necessary parts of the class through methods or properties. This helps in protecting the data from unintended interference and misuse.

Example:
        
            namespace EncapsulationExamples;

class Program
{
    static void Main(string[] args)
    {
        PersonModel person = new PersonModel();
        person.SetName("Alice");
        Console.WriteLine(person.GetName()); // Output: Alice
    }
}

class Person
{
    private string name;

    public void SetName(string name)
    {
        this.name = name;
    }

    public string GetName()
    {
        return name;
    }
}
        
    

2. Access Modifiers

Access modifiers are keywords used to specify the declared accessibility of a member or a type. The most commonly used access modifiers in C# are:

Example:
        
            namespace EncapsulationExamples;

class AccessModifiersDemo
{
    public string PublicField = "Public";
    private string PrivateField = "Private";
    protected string ProtectedField = "Protected";
    internal string InternalField = "Internal";
    protected internal string ProtectedInternalField = "Protected Internal";
    private protected string PrivateProtectedField = "Private Protected";

    public void ShowFields()
    {
        Console.WriteLine(PublicField);
        Console.WriteLine(PrivateField);
        Console.WriteLine(ProtectedField);
        Console.WriteLine(InternalField);
        Console.WriteLine(ProtectedInternalField);
        Console.WriteLine(PrivateProtectedField);
    }
}
        
    

3. Properties and Encapsulation

Properties provide a flexible mechanism to read, write, or compute the values of private fields. They are special methods called accessors. The get accessor returns the value of the private field, and the set accessor sets the value of the private field.

Example:
        
            namespace EncapsulationExamples;

class Program
{
    static void Main(string[] args)
    {
        PersonModel person = new PersonModel();
        person.Name = "Bob";
        Console.WriteLine(person.Name); // Output: Bob
    }
}

class Person
{
    private string name;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}
        
    

4. Encapsulation with Methods

Methods can also be used to encapsulate the fields of a class. They provide controlled access to the fields, allowing for validation or transformation of the data before it is returned or modified.

Example:
        
            namespace EncapsulationExamples;

class Program
{
    static void Main(string[] args)
    {
        BankAccount account = new BankAccount();
        account.Deposit(100);
        Console.WriteLine(account.GetBalance()); // Output: 100
    }
}

class BankAccount
{
    private decimal balance;

    public void Deposit(decimal amount)
    {
        if (amount > 0)
        {
            balance += amount;
        }
    }

    public decimal GetBalance()
    {
        return balance;
    }
}
        
    

5. Data Transfer Objects (DTOs) and Models

Data Transfer Objects (DTOs) and Models are both used to encapsulate data in different layers of an application. DTOs are simple objects that are used to transfer data between different parts of an application, often between layers or across network boundaries. Models, on the other hand, represent the domain data and typically include both data and behavior. DTOs are particularly useful when you want to transfer data without exposing your domain models directly, ensuring a clear separation of concerns and increasing security.

Example DTO:
        
            namespace EncapsulationExamples;

// Data Transfer Object (DTO)
class PersonDto
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        PersonDto personDto = new PersonDto
        {
            FirstName = "John",
            LastName = "Doe",
            Age = 30
        };

        Console.WriteLine($"Name: {personDto.FirstName} {personDto.LastName}, Age: {personDto.Age}");
    }
}
        
    
Example Model:
        
            namespace EncapsulationExamples;

// Domain Model
class PersonModel
{
    private string firstName;
    private string lastName;
    private int age;

    public string FirstName
    {
        get { return firstName; }
        set { firstName = value; }
    }

    public string LastName
    {
        get { return lastName; }
        set { lastName = value; }
    }

    public int Age
    {
        get { return age; }
        set
        {
            if (value >= 0)
            {
                age = value;
            }
        }
    }

    public void DisplayPerson()
    {
        Console.WriteLine($"Name: {FirstName} {LastName}, Age: {Age}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        PersonModel person = new PersonModel
        {
            FirstName = "Jane",
            LastName = "Doe",
            Age = 28
        };

        person.DisplayPerson(); // Output: Name: Jane Doe, Age: 28
    }
}
        
    

6. Mapping Between DTOs and Entities

Mapping between DTOs and Entities is essential to transform data between different layers of the application. This can be done manually or using libraries such as AutoMapper. Proper mapping ensures that data integrity is maintained and that the application layers remain decoupled.

Manual Mapping Example:
        
            namespace EncapsulationExamples;

class PersonDto
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

class PersonModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }

    public void DisplayPerson()
    {
        Console.WriteLine($"Name: {FirstName} {LastName}, Age: {Age}");
    }
}

class Mapper
{
    public static PersonModel MapToModel(PersonDto dto)
    {
        return new PersonModel
        {
            FirstName = dto.FirstName,
            LastName = dto.LastName,
            Age = dto.Age
        };
    }

    public static PersonDto MapToDto(PersonModel model)
    {
        return new PersonDto
        {
            FirstName = model.FirstName,
            LastName = model.LastName,
            Age = model.Age
        };
    }
}

class Program
{
    static void Main(string[] args)
    {
        PersonDto personDto = new PersonDto { FirstName = "John", LastName = "Doe", Age = 30 };
        PersonModel personModel = Mapper.MapToModel(personDto);
        personModel.DisplayPerson(); // Output: Name: John Doe, Age: 30
    }
}
        
    
AutoMapper Example:
        
            using AutoMapper;

namespace EncapsulationExamples;

class PersonDto
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

class PersonModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }

    public void DisplayPerson()
    {
        Console.WriteLine($"Name: {FirstName} {LastName}, Age: {Age}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        var config = new MapperConfiguration(cfg => cfg.CreateMap<PersonDto, PersonModel>());
        var mapper = config.CreateMapper();

        PersonDto personDto = new PersonDto { FirstName = "John", LastName = "Doe", Age = 30 };
        PersonModel personModel = mapper.Map<PersonModel>(personDto);
        personModel.DisplayPerson(); // Output: Name: John Doe, Age: 30
    }
}
        
    

7. Mapping Between Models and Entities

Just as with DTOs, Models often need to be mapped to and from Entities to ensure that business logic and data persistence are appropriately separated.

Manual Mapping Example:
        
            namespace EncapsulationExamples;

class PersonModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }

    public void DisplayPerson()
    {
        Console.WriteLine($"Name: {FirstName} {LastName}, Age: {Age}");
    }
}

class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }

    public void DisplayPerson()
    {
        Console.WriteLine($"Name: {FirstName} {LastName}, Age: {Age}");
    }
}

class Mapper
{
    public static Person MapToEntity(PersonModel model)
    {
        return new Person
        {
            FirstName = model.FirstName,
            LastName = model.LastName,
            Age = model.Age
        };
    }

    public static PersonModel MapToModel(Person entity)
    {
        return new PersonModel
        {
            FirstName = entity.FirstName,
            LastName = entity.LastName,
            Age = entity.Age
        };
    }
}

class Program
{
    static void Main(string[] args)
    {
        PersonModel personModel = new PersonModel { FirstName = "Jane", LastName = "Doe", Age = 28 };
        Person personEntity = Mapper.MapToEntity(personModel);
        personEntity.DisplayPerson(); // Output: Name: Jane Doe, Age: 28
    }
}
        
    
AutoMapper Example:
        
            using AutoMapper;

namespace EncapsulationExamples;

class PersonDto
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }

    public void DisplayPerson()
    {
        Console.WriteLine($"Name: {FirstName} {LastName}, Age: {Age}");
    }
}

class Program
{
    static void Main(string[] args)
    {
        var config = new MapperConfiguration(cfg => cfg.CreateMap<PersonDto, Person>());
        var mapper = config.CreateMapper();

        PersonDto personDto = new PersonDto { FirstName = "John", LastName = "Doe", Age = 30 };
        Person personEntity = mapper.Map<Person>(personDto);
        personEntity.DisplayPerson(); // Output: Name: John Doe, Age: 30
    }
}
        
    

8. Best Practices for Encapsulation


Conclusion

Encapsulation is a key principle in C# that helps manage complexity by bundling data and methods into a single unit and restricting access to certain components. By understanding access modifiers, properties, methods, and the use of DTOs and Models, you can effectively use encapsulation to build robust and maintainable applications.