Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

C# - Value Object Pattern, Data Transfer Object Pattern

5.00/5 (4 votes)
7 Feb 2023CPOL3 min read 12.9K   71  
Beginner’s tutorial on VO and DTO Patterns with examples
This is a beginner’s tutorial on Value Object (VO) Pattern and Data Transfer Object (DTO) Patterns with examples. A Value Object is an object whose equality is based on value rather than identity. A Data Transfer Object is a data container for moving data.

1. Value Object Pattern - Definition

  • Typically, when talking about “Value Object” (VO) in C#, we are thinking of a small object, whose main purpose is to hold data and has “value semantics”. That means that equality and assignment are based on value (in contrast to being based on identity/reference).
  • The main idea behind Value Objects is to make objects compare on value rather than identity (references).
  • Value Objects typically do not have any behavior except for storage, retrieval, equality comparison, and assignment.
  • Value Objects typically live in the core of the application, participating significantly in the business logic.
  • Value Objects are frequently made immutable.
  • Related patterns: Immutable Object Pattern

2. C# Structs in VO Pattern

C# structs already have value semantics, but operators == and != need to be overridden, as well as the Hash function. Here is an example of C# structs in VO pattern.

C#
public struct CarStruct
{
    public CarStruct(Char? brand, Char? model, int? year)
    {
        Brand = brand;
        Model = model;
        Year = year;
    }

    public Char? Brand { get; set; }
    public Char? Model { get; set; }
    public int? Year { get; set; }
    public override string ToString()
    {
        return $"Brand:{Brand}, Model:{Model}, Year:{Year}";
    }

    public static bool operator ==(CarStruct? b1, CarStruct? b2)
    {
        if (b1 is null)
            return b2 is null;

        return b1.Equals(b2);
    }

    public static bool operator !=(CarStruct? b1, CarStruct? b2)
    {
        return !(b1 == b2);
    }

    public override int GetHashCode()
    {
        return (Brand, Model, Year).GetHashCode();
    }
}

//=============================================
//===Sample code===============================
//--assigning struct based objects
Console.WriteLine("-----");
Console.WriteLine("Assignment of Value Object - Struct");
CarStruct car7 = new CarStruct('T', 'C', 1991);
CarStruct car8 = car7;

Console.WriteLine($"Value object, car7={car7}");
Console.WriteLine($"Value object, car8={car8}");
string? address7 = Util.GetMemoryAddressOfStruct(ref car7);
string? address8 = Util.GetMemoryAddressOfStruct(ref car8);
Console.WriteLine($"Address car7={address7}, Address car8={address8}");
Console.WriteLine();

Console.WriteLine("Equality of Value Object - Struct");
CarStruct car5 = new CarStruct('T', 'C', 1991);
CarStruct car6 = new CarStruct('T', 'C', 1991);
Console.WriteLine($"Value object, car5={car5}");
Console.WriteLine($"Value object, car6={car6}");
bool equal56 = car5 == car6;
Console.WriteLine($"Value of car5==car6:{equal56}");
Console.WriteLine();

Console.ReadLine();

//=============================================
//===Result of execution=======================
/*
-----
Assignment of Value Object - Struct
Value object, car7=Brand:T, Model:C, Year:1991
Value object, car8=Brand:T, Model:C, Year:1991
Address car7=0x5AB657E4D0, Address car8=0x5AB657E4C0

Equality of Value Object - Struct
Value object, car5=Brand:T, Model:C, Year:1991
Value object, car6=Brand:T, Model:C, Year:1991
Value of car5==car6:True
*/

3. C# Class in VO Pattern

C# classes have reference semantics, so operators Equality, operators == and != need to be overridden, as well as the Hash function. Problem is that the “assignment operator =” cannot be overloaded in C#, so we will create a copy-constructor instead. Here is an example of C# class in VO pattern:

C#
public class CarClass
{
    public CarClass(Char? brand, Char? model, int? year)
    {
        Brand = brand;
        Model = model;
        Year = year;
    }

    public CarClass(CarClass original)
    {
        Brand = original.Brand;
        Model = original.Model;
        Year = original.Year;
    }

    public Char? Brand { get; set; }
    public Char? Model { get; set; }
    public int? Year { get; set; }
    public override string ToString()
    {
        return $"Brand:{Brand}, Model:{Model}, Year:{Year}";
    }

    public static bool operator ==(CarClass? b1, CarClass? b2)
    {
        if (b1 is null)
            return b2 is null;

        return b1.Equals(b2);
    }

    public static bool operator !=(CarClass? b1, CarClass? b2)
    {
        return !(b1 == b2);
    }

    public override bool Equals(object? obj)
    {
        if (obj == null)
            return false;

        return obj is CarClass b2 ? (Brand == b2.Brand &&
                                     Model == b2.Model &&
                                     Year == b2.Year) : false;
    }

    public override int GetHashCode()
    {
        return (Brand, Model, Year).GetHashCode();
    }
}

//=============================================
//===Sample code===============================
//--assigning class based objects
Console.WriteLine("-----");
Console.WriteLine("Assignment of Value Object - Class");
CarClass car7 = new CarClass('T', 'C', 1991);
CarClass car8 = new CarClass(car7);

Console.WriteLine($"Value object, car7={car7}");
Console.WriteLine($"Value object, car8={car8}");

Tuple<string?, string?> addresses1 =Util.GetMemoryAddressOfClass(car7, car8);
Console.WriteLine($"Address car7={addresses1.Item1}, Address car8={addresses1.Item2}");
Console.WriteLine();

Console.WriteLine("Equality of Value Object - Class");
CarClass car5 = new CarClass('T', 'C', 1991);
CarClass car6 = new CarClass('T', 'C', 1991);
Console.WriteLine($"Value object, car5={car5}");
Console.WriteLine($"Value object, car6={car6}");
bool equal56 = car5 == car6;
Console.WriteLine($"Value of car5==car6:{equal56}");
Console.WriteLine();

Console.ReadLine();

//=============================================
//===Result of execution=======================
/*
-----
Assignment of Value Object - Class
Value object, car7=Brand:T, Model:C, Year:1991
Value object, car8=Brand:T, Model:C, Year:1991
Address car7=0x1ED375614E0, Address car8=0x1ED37561500

Equality of Value Object - Class
Value object, car5=Brand:T, Model:C, Year:1991
Value object, car6=Brand:T, Model:C, Year:1991
Value of car5==car6:True
*/

4. Data Transfer Object Pattern - Definition

  • Typically, when talking about “Data Transfer Object” (DTO) in C#, we are thinking of an object whose primary purpose is to act as a container for data that are to be transferred.
  • The main idea behind Data Transfer Objects is to facilitate/simplify data transfer between layers/boundaries of the system. Typically, they achieve that by aggregating data that would be otherwise transferred in several calls.
  • Data Transfer Objects typically do not have any behavior except for storage, retrieval, serialization, and deserialization.
  • Data Transfer Objects typically live on the boundaries of the layers/system.
  • Data Transfer Objects are usually not immutable since they do not benefit from immutability.
  • Related patterns: Façade Pattern. That is because DTO frequently aggregates parts of several objects that need to be transferred.

5. C# Structs/Classes in DTO Pattern

Typically, communication between boundaries of layers/systems is a time-consuming operation. There, it is of interest to reduce the number of calls over boundaries or between layers. That is a motivation to use DTO object that aggregates data belonging to several objects, that would otherwise need to be transported in several calls. Here is one example of a DTO object.

C#
public class Person
{
    public Person(int id, String name, String nationality,  int age)
    {
        Id =id;
        Name =name;
        Nationality =nationality;
        Age =age;
    }

    public int Id { get; set; }
    public String Name { get; set; }    
    public String Nationality { get; set;}
    public int Age { get; set;} 
}

public class Address
{
    public Address(int id, String street, String city)
    {
        Id=id;
        Street=street;
        City=city;
    }    

    public int Id { get; set; }
    public string Street { get; set; }
    public string City { get; set; }    
}

public class Position
{
    public Position(int id, String title, int salary)
    {
        Id=id; Title=title;Salary=salary;
    }

    public int Id { get; set; }
    public String Title { get; set; }
    public int Salary { get; set;}
}
    
public class EmployeeDto
{
    public EmployeeDto()
    {}
    public EmployeeDto(Person person, Address address, Position position)
    {
        Name = person.Name;
        City = address.City;
        Title = position.Title;
    }

    public string? Name { get; set; }
    public string?  City { get; set; }
    public string? Title { get; set; }
    public override string ToString()
    {
        return $"Name:{Name}, City:{City}, Title:{Title}";
    }

    public byte[]? Serialize()
    {
        byte[]? result = null;
        string json = JsonSerializer.Serialize(this);
        result=System.Text.Encoding.Unicode.GetBytes(json);
        return result;
    }

    public static EmployeeDto? Deserialize(byte[]? data)
    {
        EmployeeDto? result = null;
        if (data != null)
        {
            string original2 = System.Text.Encoding.Unicode.GetString(data);
            result = JsonSerializer.Deserialize<EmployeeDto>(original2);
        }
        return result;
    }
}    

//=============================================
//===Sample code===============================
Console.WriteLine("-----");
Person p = new Person(111, "Rafael", "Spanish", 36);
Address a = new Address(222, "Rafa's Way", "Majorca");
Position po = new Position(333, "Senior Programmer", 50_000);

EmployeeDto e1 = new EmployeeDto(p, a, po);

byte[]? emplyeeData1 = e1.Serialize();
//-------transfer over wire-------------

EmployeeDto? e2 = EmployeeDto.Deserialize(emplyeeData1);

Console.WriteLine($"Original DTO, e1={e1}");
Console.WriteLine($"Received DTO, e2={e2?.ToString() ?? String.Empty}");
Console.WriteLine();

Console.ReadLine();
//=============================================
//===Result of execution=======================
/*
-----
Original DTO, e1=Name:Rafael, City:Majorca, Title:Senior Programmer
Received DTO, e2=Name:Rafael, City:Majorca, Title:Senior Programmer
*/

6. Utility for Finding Object Addresses

We developed a small utility that will give us the address of the objects in question, so by comparing addresses, it will be easily seen if we are talking about the same or different objects. The only problem is that our address-finding-utility has a limitation, that is, it works ONLY for objects on the heap that do not contain other objects on the heap (references). Therefore, we are forced to use only primitive values in our objects, and that is the reason why I needed to avoid using C# string and am using only char types.

C#
public class Util
{
    public static Tuple<string?, string?> GetMemoryAddressOfClass<T1, T2>(T1 o1, T2 o2)
        where T1 : class
        where T2 : class
    {
        //using generics to block structs, that would be boxed
        //so we would get address of a boxed object, not struct
        //works only for objects that do not contain references
        // to other objects
        string? address1 = null;
        string? address2 = null;

        GCHandle? handleO1 = null;
        GCHandle? handleO2 = null;

        if (o1 != null)
        {
            handleO1 = GCHandle.Alloc(o1, GCHandleType.Pinned);
        }

        if (o2 != null)
        {
            handleO2 = GCHandle.Alloc(o2, GCHandleType.Pinned);
        }

        if (handleO1 != null)
        {
            IntPtr pointer1 = handleO1.Value.AddrOfPinnedObject();
            address1 = "0x" + pointer1.ToString("X");
        }

        if (handleO2 != null)
        {
            IntPtr pointer2 = handleO2.Value.AddrOfPinnedObject();
            address2 = "0x" + pointer2.ToString("X");
        }

        if (handleO1 != null)
        {
            handleO1.Value.Free();
        }

        if (handleO2 != null)
        {
            handleO2.Value.Free();
        }

        Tuple<string?, string?> result = 
                       new Tuple<string?, string?>(address1, address2);

        return result;
    }

    public static unsafe string? GetMemoryAddressOfStruct<T1>(ref T1 o1)
        where T1 : unmanaged
    {
        //In order to satisfy this constraint "unmanaged" a type must be a struct
        //and all the fields of the type must be unmanaged
        //using ref, so I would not get a value copy
        string? result = null;
        fixed (void* pointer1 = (&o1))
        {
            result = $"0x{(long)pointer1:X}";
        }

        return result;
    }
}

7. Conclusion

A Value Object (VO) is an object whose equality is based on value rather than identity. A Data Transfer Object (DTO) is a data container for moving data, whose purpose is to simplify data transfer between layers. These two pattern names are sometimes used interchangeably.

Value Object (VO) pattern and Data Transfer Object (DTO) pattern, although not very complicated, are frequently used and need to be in the repertoire of any serious C# programmer.

Related topics are “Immutable Object pattern” and “Records in C#”.

8. References

History

  • 8th February, 2023: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)