Introduction
Mapping one object to another is a common task in software development. It involves copying the values of properties from one object to another object of a different type or the same type. In C#, there are several ways to map one object to another, such as AutoMapper, ValueInjecter, etc. However, this article will discuss how to map one object to another in C# without using third-party packages.
In this article, we will map one object to another object, which are the same type and different type. Some properties are same, and some are different in case.
For this, we are going to create three different classes. First is Department, which contains DepartmentId and DepartmentName.
public class Department {
public int DepartmentId {
get;
set;
}
public string DepartmentName {
get;
set;
}
}
Second is Employee, which contains various types of properties like integer, string, double, DateTime, and also Department.
public class Employee {
public int EmployeeId {
get;
set;
}
public string FirstName {
get;
set;
}
public string LastName {
get;
set;
}
public string AddressLine1 {
get;
set;
}
public string AddressLine2 {
get;
set;
}
public string City {
get;
set;
}
public string State {
get;
set;
}
public string Zip {
get;
set;
}
public double Salary {
get;
set;
}
public DateTime Dob {
get;
set;
}
public Department Department {
get;
set;
}
}
The third class is NewEmployee which has the same properties as Employee, but here are some properties that are different in case. Also, there are some Nullable properties.
public class NewEmployee {
public int ? EmployeeId {
get;
set;
}
public string firstname {
get;
set;
}
public string lastname {
get;
set;
}
public string addressline1 {
get;
set;
}
public string AddressLine2 {
get;
set;
}
public string City {
get;
set;
}
public string State {
get;
set;
}
public string Zip {
get;
set;
}
public double Salary {
get;
set;
}
public DateTime Dob {
get;
set;
}
public Department Department {
get;
set;
}
}
Next, we will create the object of the Employee class with dummy data, as you can see in the below code snippet.
Next, we will create one generic method called Map, which can take any kind of object as input and return given type of data.
TDATA Map < TDATA > (object oldObject) where TDATA: new() {
// Create a new object of type TDATA
TDATA newObject = new TDATA();
try {
// If the old object is null, just return the new object
if (oldObject == null) return newObject;
// Get the type of the new object and the type of the old object passed in
Type newObjType = typeof(TDATA);
Type oldObjType = oldObject.GetType();
// Get a list of all the properties in the new object
var propertyList = newObjType.GetProperties();
// If the new object has properties
if (propertyList.Length > 0) {
// Loop through each property in the new object
foreach(var newObjProp in propertyList) {
// Get the corresponding property in the old object
var oldProp = oldObjType.GetProperty(newObjProp.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.ExactBinding);
// If there is a corresponding property in the old object and it can be read and the new object's property can be written to
if (oldProp != null && oldProp.CanRead && newObjProp.CanWrite) {
// assign property type of both object to new variables
var oldPropertyType = oldProp.PropertyType;
var newPropertyType = newObjProp.PropertyType;
//check if property is nullable or not. if property is nullable then get it's original data type from generic argument
if (oldPropertyType.IsGenericType && oldPropertyType.GetGenericTypeDefinition() == typeof(Nullable < > )) oldPropertyType = oldPropertyType.GetGenericArguments()[0];
if (newPropertyType.IsGenericType && newPropertyType.GetGenericTypeDefinition() == typeof(Nullable < > )) newPropertyType = newPropertyType.GetGenericArguments()[0];
//check type of both property if match then set value
if (newPropertyType == oldPropertyType) {
// Get the value of the property in the old object
var value = oldProp.GetValue(oldObject);
// Set the value of the property in the new object
newObjProp.SetValue(newObject, value);
}
}
}
}
} catch (Exception ex) {
// If there is an exception, log it
}
// Return the new object
return newObject;
}
As you can see in the above code
- The name of the method is Map, and it is generic, meaning that it can work with any type TDATA.
- This method accepts one parameter of object type, so it can accept any kind of data
- where TDATA: new(): This is a constraint on the generic type TDATA, which ensures that the type has a default constructor. This is necessary because we need to create a new object of type TDATA and copy the values from oldObject to the new object
Create a new object of TDATA in which we will copy data and return it at last.
Then try and catch block starts, and all the code will be inside this block.
// If the old object is null, just return the new object
if (oldObject == null)
return newObject;
Return newObject without copying any data if the old object is null
// Get the type of the new object and the type of the old object passed in
Type newObjType = typeof(TDATA);
Type oldObjType = oldObject.GetType();
The above code get the type of TDATA and input object, which is oldObject, and assigns it to newObjType and oldObjType, respectively.
Get the list of all properties of the new object. And check there are any properties exist.
// Get a list of all the properties in the new object
var propertyList = newObjType.GetProperties();
// If the new object has properties
if (propertyList.Length > 0)
Start foreach loop iteration of the property list. Get property of old object using GetProprty method. It takes the property name as a parameter which is required, and the second one is BindingFlags which is optional.
// Get the corresponding property in the old object
var oldProp = oldObjType.GetProperty(newObjProp.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.ExactBinding);
BindingFlags
is an enumeration that is used to specify the attributes of a member that should be considered when searching for members in reflection. The vertical bars |
are used to combine multiple enumeration values into a single value.
- BindingFlags.Public: Specifies that public members should be included in the search.
- BindingFlags.NonPublic: Specifies that non-public members should be included in the search.
- BindingFlags.Instance: Specifies that instance members should be included in the search.
- BindingFlags.IgnoreCase: Specifies that the search should be case-insensitive. This flag is used when we want to map FirstName and firstname.
- BindingFlags.ExactBinding: Specifies that the search should only return members matching the specified criteria.
By combining these values with the vertical bars, we are telling the reflection API to search for members that are both public and non-public, instance members only (not static), case-insensitive, and that exactly match the specified criteria. This allows us to find the correct property in the source object even if the casing of its name differs from the target object's property.
// If there is a corresponding property in the old object and it can be read and the new object's property can be written to
if (oldProp != null && oldProp.CanRead && newObjProp.CanWrite)
Next, check that Old Object has the same properties as the new one by checking that oldProp is null. Here we will check two more flags: the property of a new object is writable, and the property of an old object is readable. If we continue without this check, it will throw an error when we get value from the old object and set it in the new object.
// assign property type of both object to new variables
var oldPropertyType; = oldProp.PropertyType
var newPropertyType = newObjProp.PropertyType;
//check if property is nullable or not. if property is nullable then get it's original data type from generic argument
if (oldPropertyType.IsGenericType && oldPropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
oldPropertyType = oldPropertyType.GetGenericArguments()[0];
if (newPropertyType.IsGenericType && newPropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
newPropertyType = newPropertyType.GetGenericArguments()[0];
Create two new variables oldPropertyType and newPropertyType, and assign type of old and new object property, respectively.
Then we are also going to add a new check that if any property is a generic type and specifically Nullable, then we are going to re-assign the property type variable with its original data type by GetGenericArguments method, which returns an array, and we are going to get first value.
Next, check that both old and new property are the same type. If we do not add this check, we will get an error when the string type property tries to set a value in the integer property.
Get value from the old object by the GetValue method.
// Get the value of the property in the old object
var value = oldProp.GetValue(oldObject);
Set value in the new object by the SetValue method.
// Set the value of the property in the new object
newObjProp.SetValue(newObject, value);
In the last return newObject.
Main employee Object
Try to map the employee object in the new object, which is the same type as the employee object.
//map with same type of object
Employee employee1 = Map<Employee>(employee);
Try to Map employee object in new object, which are different type. The Newemployee object is the NewEmployee type.
//map with diffrent type of object
NewEmployee newEmployee = Map<NewEmployee>(employee);
Overall, the Map
method uses reflection to copy the properties of one object to another object, which may or may not be of the same type. This allows for flexible object mapping without the need for a third-party library.
You can access and download the source code of this from my GitHub.