Friday, October 18, 2013

Custom Shallow and Deep Copy Extension Methods for .NET

Some times you are working with an object and you want to copy it to modify its properties for whatever reason. You want to do it without modifying the original. To illustrate, look at the fallowing scenario:

1 2 3 4 public class Person
{
public string Name { get; set; }
}

1 2 3 4 5 6 7 8 9 var o = new Person();
o.Name = "John";

Person p = o;

p.Name = "Mary";

Response.Write(o.Name); //Output value is: Mary
Response.Write(p.Name); //Output value is: Mary

So, you ask, what happened to "John"? Well, you modified it. The problem is that the new person "p" in line# 4 is only a reference to variable "o". In essence, by modifying "p" you are modifying "o".
Now, if you have access to the code, you can turn it into a struct to avoid this from happening:

1 2 3 4 public struct Person
{
public string Name { get; set; }
}

Well, if you don't want a struct (Structure in VB), what you can do is add a function to the class to return a new copy of itself:

1 2 3 4 5 6 7 8 9 public class Person
{
public string Name { get; set; }

public Person GetShallowCopy()
{
return new Person() { Name = this.Name };
}
}

1 2 3 4 5 6 7 8 9 var o = new Person();
o.Name = "John";

Person p = o.GetShallowCopy();

p.Name = "Mary";

Response.Write(o.Name); //Output value is: John
Response.Write(p.Name); //Output value is: Mary


As you can see, the GetShallowCopy() function creates a new object and copies the current values of its own container class. One problem with this is that you might have way too many properties or way too many classes and you don't want to do this in all of your classes. Another problem is that you might not have access to the code of the class that you want to shallow copy. For these scenarios, I use extension methods and reflection.

Create an extension method that looks like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public static class Extensions
{

public static T GetShallowCopy<T>(this T obj)
{
T newObj = Activator.CreateInstance<T>();

var props = typeof(T).GetProperties();
var flds = typeof(T).GetFields();

foreach (PropertyInfo i in props)
{
if (i.CanRead && i.CanWrite)
{
object val = i.GetValue(obj, null);
Type typ = i.PropertyType;

if (typ.IsPrimitive || typ == typeof(string) || typ == typeof(DateTime))
{
i.SetValue(newObj, val, null);
}
}
}

foreach (FieldInfo i in flds)
{
object val = i.GetValue(obj);
Type typ = i.FieldType;

if (typ.IsPrimitive || typ == typeof(string) || typ == typeof(DateTime))
{
i.SetValue(newObj, val);
}
}

return newObj;
}

}


VB.NET

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 Public Module Extensions

<Extension()> _
Public Function GetShallowCopy(Of T)(ByRef obj As T) As T
Dim newObj = Activator.CreateInstance(Of T)()

Dim props = GetType(T).GetProperties()
Dim flds = GetType(T).GetFields()

For Each i As PropertyInfo In props
If i.CanRead AndAlso i.CanWrite Then
Dim val As Object = i.GetValue(obj, Nothing)
Dim typ As Type = i.PropertyType

If typ.IsPrimitive OrElse typ = GetType(String) OrElse typ = GetType(DateTime) Then
i.SetValue(newObj, val, Nothing)
End If
End If
Next

For Each i As FieldInfo In flds
Dim val As Object = i.GetValue(obj)
Dim typ As Type = i.FieldType

If typ.IsPrimitive OrElse typ = GetType(String) OrElse typ = GetType(DateTime) Then
i.SetValue(newObj, val)
End If
Next

Return newObj
End Function

End Module


Here is a deep copy function too:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public static Extensions
{
public static T GetDeepCopy<T>(this T obj)
{
var newObj = Activator.CreateInstance(obj.GetType());

var props = obj.GetType().GetProperties();
var flds = obj.GetType().GetFields();

foreach (PropertyInfo i in props)
{
if (i.CanRead)
{
object val = i.GetValue(obj, null);
if (val == null) { continue; }

if (val is string || val.GetType().IsPrimitive)
{
if (i.CanWrite)
{
i.SetValue(newObj, val, null);
}
}
else
{
var n = val.GetDeepCopy();
if (i.CanWrite)
{
i.SetValue(newObj, n, null);
}
}
}
}

foreach (FieldInfo i in flds)
{
object val = i.GetValue(obj);
if (val == null) { continue; }

if (val is string || val.GetType().IsPrimitive)
{
i.SetValue(newObj, val);
}
else
{
var n = val.GetDeepCopy();
i.SetValue(newObj, n);
}
}

return (T)newObj;
}
}


VB.NET

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 Public Module Extensions

<Extension()> _
Public Function GetDeepCopy(Of T)(ByRef obj As T) As T
Dim newObj = Activator.CreateInstance(obj.GetType())

Dim props = obj.GetType().GetProperties()
Dim flds = obj.GetType().GetFields()

For Each i As PropertyInfo In props
If i.CanRead Then
Dim val As Object = i.GetValue(obj, Nothing)
If val Is Nothing Then Continue For

Dim typ As Type = i.PropertyType

If typ.IsPrimitive OrElse typ = GetType(String) OrElse typ = GetType(DateTime) Then
If i.CanWrite Then
i.SetValue(newObj, val, Nothing)
End If
Else
Dim n = GetDeepCopy(val)
If i.CanWrite Then
i.SetValue(newObj, n, Nothing)
End If
End If
End If
Next

For Each i As FieldInfo In flds
Dim val As Object = i.GetValue(obj)
If val Is Nothing Then Continue For

Dim typ As Type = i.FieldType

If typ.IsPrimitive OrElse typ = GetType(String) OrElse typ = GetType(DateTime) Then
i.SetValue(newObj, val)
Else
Dim n = GetDeepCopy(val)
i.SetValue(newObj, n)
End If
Next

Return CType(newObj, T)
End Function

End Module

No comments:

Post a Comment