Jorge Serrano
  • Home

Shallow Copy y Deep Copy en C#

  • By jorge
  • May-6-2020
  • Arquitectura, C#
  • 0 Comments.

Introducción

Cuando trabajamos con objetos en cualquier lenguaje de programación, tendemos a trabajar con ellos, realizar operaciones con o sobre ellos, modificar sus datos, etc.

Hace unos años publiqué una entrada sobre la inmutabilidad de objetos y sus propiedades en C#.
En aquella entrada abordaba la inmutabilidad de objetos y propiedades, y cómo podemos creer estar ante un objeto y propiedad inmutable pero no ser realmente así sino consideramos algunas cosas.

La idea de un objeto inmutable es que su estado y valores sean establecidas en la inicialización del objeto, y que una vez inicializado éste, no debería permitirse su modificación.

De la misma forma, la inmutabilidad de propiedades hace referencia a que sus propiedades sean de sólo lectura.

Ambas combinaciones deben permanecer sólidas para asegurar la inmutabilidad de un objeto, pero dentro del mundo de la programación orientada a objetos existen otras consideraciones cuando trabajamos con objetos y propiedades, y hacer que una propiedad sea de sólo lectura no nos asegura que sea inmutable como demostré en aquella ocasión.

Es por lo tanto nuestra obligación como desarrolladores Software, considerar todos los escenarios posibles, incluida la manipulación o copia de objetos, pudiendo encontrarnos con situaciones que debemos controlar.

Lo que a continuación voy a explicar tiene relación con inmutabilidad y con lo que se denomina como Shallow Copy y Deep Copy.
Vamos con ello.

 

Tipos por valor y tipos por refencia

Una de las consideraciones más importantes es entender el concepto de tipos por valor, tipos por referencia, y cuando aplican.

Seguramente lo sepas, pero por si acaso, voy a hacer un repaso muy rápido sobre estos conceptos.

Un tipo por valor contiene el valor o dato en sí mismo.
Su valor es asignado de forma directa e inmutable.

Un tipo por referencia contiene una referencia o puntero a su dato.
Por lo que apuntamos a ese dato, que en el caso de que cambie, su puntero es el que apunta al dato, por lo que si hacemos referencia a él desde más de un sitio, ambos sitios apuntarán a la dirección de memoria en la que se encuentra el dato.

Los tipos por valor pueden ser de dos clases: tipo de estructura, y tipo enumerable.
También tenemos en este grupo a los tipos simples (tipos numéricos, en coma flotante, bool, char), y las variables.

Los tipos por referencia pueden ser: clases, interfaces, delegados, tipos dynamic, object o string.

 

Shallow Copy y Deep Copy

Una vez sentadas o repasadas las bases principales, inmutabilidad, tipos por valor y tipos por referencia, vamos a hablar de estos dos conceptos.

Estos conceptos vienen de la época de SmallTalk, aproximadamente hablo de la década de los 80.

Términos y conceptos aplicables a la programación orientada a objetos actual.

Ambos términos, tienen que ver o están relacionados con la copia de objetos, pero ambos son diferentes.

En la imagen que acompaña la cabecera de esta entrada se resumen visualmente ambos, pero hablaré más sobre ellos para entender mejor su alcance y consecuencias de forma práctica.

 

Copia de objetos

A la hora de copiar objetos, es necesario tener en consideración si nos encontramos ante un tipo por valor o un tipo por referencia, ya que el comportamiento y objetivos que buscamos, pueden verse afectados.

Supongamos esta clase inmutable:

public class MyImmutableObject
{
    private readonly string _name;
    public string Name => _name;

    private readonly List _things;
    public List Things => _things;

    public MyImmutableObject(string name, List things)
    {
        _name = name;
        _things = things;
    }
}

La naturaleza del tipo por referencia Things que está contenido en el objeto inmutable, no me impide que haga algo parecido a:

var immutableObject = new MyImmutableObject("Juan", new List() { "Uno", "Dos" });
immutableObject.Things.Add("Other");

Esta operación que hemos realizado es un acceso directo al objeto, el cuál es un tipo por referencia, es decir, un puntero al objeto.
Por lo que operaciones como estas, aunque la propiedad sea de sólo lectura, están permitidas cuando trabajamos con C#.

Algo similar ocurre cuando hacemos esto otro:

var immutableObject = new MyImmutableObject("Juan", new List() { "Uno", "Dos" });
var things = immutableObject.Things;
things.Add("Other");

Aquí, lo que estamos haciendo es algo parecido a lo anterior, aunque sensiblemente diferente a primera vista.

Para entender bien este comportamiento y lo que tiene que ver con Shallow Copy y Deep Copy, lo que se establece por debajo en lo que hemos visto es lo que se denomina como Shallow Copy o Copia Superficial.

Existen lenguajes de programación que por defecto funcionan así, y otros, permiten indicar el tipo de copia que queremos realizar.

En nuestro caso y por defecto en C#, realizamos una copia superficial.
Copiamos el puntero al dato por ser un tipo por referencia, pudiendo actuar sobre él directamente.

Como podemos observar, si deseamos trabajar con objetos inmutables, esto se nos viene ligeramente abajo en lo que a inmutabilidad se refiere cuando trabajamos con tipos por referencia.

¿Cómo resolver esto?.
Pues básicamente realizando una Deep Copy o Copia Profunda de nuestra colección.

Una Deep Copy establece una copia de los elementos que forman parte del objeto, pero omitiendo la copia de sus referencias.
Es decir, creando un nuevo objeto en memoria, copiando o clonando su original, lo que representa un snapshoot del objeto en sí mismo en un momento dado.

La forma más sencilla de hacer un Deep Copy es recorriendo todos y cada uno de los elementos que forman parte de nuestro objeto original para crear el nuevo objeto.
Sin embargo, podríamos incurrir en creer que estamos haciendo un Deep Copy de un tipo por referencia cuando en realidad estaríamos haciendo un Shallow Copy.

Pongamos en práctica esto que comento con el siguiente código para enteder mejor esta implicación:

public class MyImmutableObject
{
    private readonly string _name;
    public string Name => _name;

    private readonly List _things;
    public List Things => _things;

    public MyImmutableObject(string name, List things)
    {
        _name = name;
        _things = things;
    }
}

public static void Main(string[] args)
{
    var immutableObject = new MyImmutableObject("Juan", new List() { "Uno", "Dos" });
    var otherImmutableObject = new MyImmutableObject("Luis", immutableObject.Things);
    otherImmutableObject.Things.Add("Other");

    Console.WriteLine(immutableObject.Name);
    foreach (var item in immutableObject.Things)
        Console.WriteLine(item);

    Console.WriteLine();

    Console.WriteLine(otherImmutableObject.Name);
    foreach (var item in otherImmutableObject.Things)
        Console.WriteLine(item);

    Console.ReadKey();
}

En este ejemplo, hemos tratado (o era la intención) de hacer una copia de sus datos, sin embargo y en el caso de la colección, no estaremos haciendo un Deep Copy, sino un Shallow Copy, incurriendo en los problemas que antes comentaba.

Es decir, estamos haciendo una copia de la referencia al valor original.

Como podemos apreciar, es un tema delicado sobre el cuál es muy fácil despistarse y cometer algún error.

Una de las formas más adecuadas de resolver esto en C# es a través de una extensión genérica que nos permita crear un Deep Copy en toda regla utilizando Reflection para ello.
De esta manera, evitaremos los problemas que estoy mostrando en esta entrada.

Así que imaginemos que tenemos esa extensión (que ahora no voy a poner aquí).

Nuestro código debería ser similar ahora a:

public class MyImmutableObject
{
    private readonly string _name;
    public string Name => _name;

    private readonly List _things;
    public List Things => _things.DeepCopy();

    public MyImmutableObject(string name, List things)
    {
        _name = name;
        _things = things;
    }
}

Ahora, omitiendo aún esa supuesta extensión, una aproximación aún mas sencilla sin la teórica extensión que haría esa acción sería de esta forma:

public class MyImmutableObject
{
    private readonly string _name;
    public string Name => _name;

    private readonly List _things;
    public List Things => new List(_things);

    public MyImmutableObject(string name, List things)
    {
        _name = name;
        _things = things;
    }
}

Lo que recibiremos al llamar a Things es una nueva copia del objeto (o mejor dicho, una creación nueva) referenciando a otra posición de memoria diferente a la del original.
Es decir, habremos aislado completamente el tipo por referencia original creando una Deep Copy de los valores de esa propiedad.

Ahora bien, haciendo uso de una extensión genérica muy sencilla para abordar ese comportamiento con las colecciones, podría ser el siguiente:

public static class DeepCopyExtensions
{
    public static IList DeepCopy(this IList originalCollection) where T : ICloneable => 
        originalCollection.Select(item => (T)item.Clone()).ToList();
}

public class MyImmutableObject
{
    private readonly string _name;
    public string Name => _name;

    private readonly List _things;
    public List Things => _things.DeepCopy().ToList();

    public MyImmutableObject(string name, List things)
    {
        _name = name;
        _things = things;
    }
}

Conclusiones

Todo esto es lo que debemos tener en cuenta cuando trabajamos con objectos inmutables y cuando copiamos sus valores.

Es muy fácil errar como vemos.

Entender bien no sólo lo que es y significa inmutabilidad, tipos por valor y tipos por referencia, no es suficiente para comprender todo lo que sucede «por debajo». Conceptos como Shallow Copy o Deep Copy nos ayuda a comprender mejor aún este tipo de comportamientos.

Recordemos que una Shallow Copy copia las referencias a los elementos y los valores de los elementos si los hubiera.
Una Deep Copy crea nuevas referencias y copia los valores de los elementos si los hubiera.

Como digo, la fiabilidad es clave y nuestra responsabilidad como desarrolladores debe asegurar que el propósito de nuestro Software, cumple perfectamente con lo esperado.

Espero que esta entrada sea de utilidad.

Happy Coding!

Comments

Deja un comentario Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

← Previous Post Next Post →

Jorge Serrano

MVP Reconnect


¡Subscríbete a mi canal!
YouTube

Donaciones
Donation

Entradas recientes

  • Go – Arrays
  • Go – Operators
  • Go – Constants
  • Go – Tipos de Datos
  • Go – Variables
  • Hello Go-rld!
  • Introducción a Go o Golang
  • JSON Patch en ASP.NET Core 5 Web API
  • Null Checking en C#
  • ¿Porqué mi página web por defecto de ASP.NET Core no se vé en mi Azure Web App y me da un 404?

Categorías

  • .NET 5
  • .NET Core
  • .NET Core 3.0
  • .NET Core 3.1
  • .NET Framework 2.0
  • .NET Framework 3.0
  • .NET Framework 3.5
  • .NET Framework 4.0
  • .NET Framework 4.5
  • .NET Framework 4.6
  • .NET Framework 4.7
  • .NET Framework 4.8
  • .NET Standard 2.0
  • .NET Standard 2.1
  • AMQP
  • Android
  • Angular
  • API REST
  • Apple
  • Apple iOS
  • Apple macOs
  • Arquitectura
  • ASP.NET
  • ASP.NET Core
  • ASP.NET Core 3
  • ASP.NET Core 5
  • AWS
  • Azure App Service
  • Azure Application Insights
  • Azure Cosmos DB
  • Azure Database Migration Service
  • Azure Databricks
  • Azure DevOps
  • Azure Event Grid
  • Azure Functions
  • Azure IoT
  • Azure Portal
  • Azure PowerShell
  • Azure Queue Storage
  • Azure SQL
  • Azure Storage
  • Azure Virtual Datacenter
  • Azure WebApps
  • Big Data
  • Bing
  • Blazor
  • Blog
  • Bots
  • C#
  • C# 7.0
  • C# 7.1
  • C# 7.2
  • C# 7.3
  • C# 8.0
  • C# 9.0
  • Channel 9
  • Codeplex
  • Codespaces
  • Containers
  • Debugging
  • DevOps
  • Docker
  • Electron
  • Entity Framework
  • Entity Framework Core
  • Entity Framework Core 3.0
  • Entity Framework Core 5
  • Eventos
  • F#
  • FaaS
  • FeatureFlags
  • FeatureToggles
  • Feeds
  • Fluent Assertions
  • General
  • GIMP
  • Git
  • GitHub
  • Go
  • Google
  • Google Analytics
  • Gradle
  • gRPC
  • GSA
  • Historia de la Informática
  • HoloLens
  • HtmlAgilityPack
  • IdentityServer4
  • Inkscape
  • Ionic
  • iOS
  • IoT
  • Java
  • JavaScript
  • JDBC
  • JSON
  • Kubernetes
  • Lenguajes de Programación
  • Libros y Cursos
  • LINQ
  • Linux
  • LiteDB
  • Machine Learning
  • macOS
  • Microservices
  • Microsoft
  • Microsoft .NET Framework 4.5
  • Microsoft 365
  • Microsoft Azure
  • Microsoft Build
  • Microsoft Ignite
  • Microsoft Learn
  • Microsoft Orleans
  • Microsoft Surface Go
  • Microsoft Teams
  • ML.NET
  • MQTT
  • MRO
  • MS-DOS
  • MsCoders Madrid
  • MVP
  • NancyFx
  • Node.js
  • NoSQL
  • NuGet
  • NUnit
  • OData
  • ODP.NET Core
  • Office 2007
  • Office 2010
  • Office 2013
  • Office 2016
  • Office 2019
  • Office 365
  • Open Source
  • Open XML SDK
  • Opinión
  • Orchard CMS
  • OT
  • PaaS
  • Patterns
  • PdfSharpCore
  • Performance
  • PHP
  • Postman
  • Power BI
  • PowerShell
  • PowerShell Core
  • Productividad
  • Project Server 2019
  • R
  • Rendimiento
  • Scala
  • Scraper
  • Security
  • Serverless
  • Service Fabric
  • SharePoint Server 2019
  • SignalR
  • Sin categoría
  • Sistemas Distribuidos
  • Skype
  • Skype for Business Server 2019
  • Small Basic Online
  • SQL Server 2005
  • SQL Server 2008
  • SQL Server 2012
  • SQL Server 2014
  • SQL Server 2016
  • SQL Server 2017
  • SQL Server 2019
  • STOMP
  • Swagger
  • Testing
  • TFS 2017
  • TFS 2018
  • Tools
  • TypeScript
  • Unity
  • UWP
  • UX
  • Visio
  • Visual Basic
  • Visual Studio 2010
  • Visual Studio 2012
  • Visual Studio 2013
  • Visual Studio 2015
  • Visual Studio 2017
  • Visual Studio 2017 for Mac
  • Visual Studio 2019
  • Visual Studio 2019 for Mac
  • Visual Studio App Center
  • Visual Studio Code
  • Visual Studio IntelliCode
  • Visual Studio Live Share
  • Visual Studio Live Share Audio
  • Visual Studio Online
  • VS Anywhere
  • Vue.js
  • Web API
  • WebAssembly
  • WinDbg
  • Windows
  • Windows 10
  • Windows Compatibility Pack
  • Windows Phone 10
  • Windows Phone 7
  • Windows Phone 8
  • Windows Server 2008
  • Windows Server 2012
  • Windows Server 2016
  • Windows Server 2019
  • Windows Service
  • WinForms
  • WinUI
  • WPF
  • Xamarin
  • Xbox
  • Xcode
  • Xiaomi Mi Band 2
  • xUnit
  • YAML

Archivos

  • enero 2021
  • diciembre 2020
  • noviembre 2020
  • octubre 2020
  • septiembre 2020
  • agosto 2020
  • julio 2020
  • junio 2020
  • mayo 2020
  • abril 2020
  • marzo 2020
  • febrero 2020
  • enero 2020
  • diciembre 2019
  • noviembre 2019
  • octubre 2019
  • septiembre 2019
  • agosto 2019
  • julio 2019
  • junio 2019
  • mayo 2019
  • abril 2019
  • marzo 2019
  • febrero 2019
  • enero 2019
  • diciembre 2018
  • noviembre 2018
  • octubre 2018
  • septiembre 2018
  • agosto 2018
  • julio 2018
  • junio 2018
  • mayo 2018
  • abril 2018
  • marzo 2018
  • febrero 2018
  • enero 2018
  • diciembre 2017
  • noviembre 2017
  • octubre 2017
  • septiembre 2017
  • agosto 2017
  • julio 2017
  • junio 2017
  • febrero 2015
  • octubre 2014
  • junio 2014
  • marzo 2014
  • febrero 2014
  • enero 2014
  • diciembre 2013
  • septiembre 2013
  • agosto 2013
  • julio 2013
  • junio 2013
  • abril 2013
  • febrero 2013
  • enero 2013
  • diciembre 2012
  • noviembre 2012
  • septiembre 2012
  • agosto 2012
  • junio 2012
  • mayo 2012
  • abril 2012
  • marzo 2012
  • febrero 2012
  • enero 2012
  • diciembre 2011
  • noviembre 2011
  • octubre 2011
  • septiembre 2011
  • agosto 2011
  • julio 2011
  • junio 2011
  • mayo 2011
  • abril 2011
  • marzo 2011
  • enero 2011
  • diciembre 2010
  • noviembre 2010
  • octubre 2010
  • septiembre 2010
  • agosto 2010
  • julio 2010
  • junio 2010
  • mayo 2010
  • abril 2010
  • marzo 2010
  • febrero 2010
  • enero 2010
  • diciembre 2009
  • noviembre 2009
  • octubre 2009
  • septiembre 2009
  • agosto 2009
  • julio 2009
  • junio 2009
  • mayo 2009
  • abril 2009
  • marzo 2009
  • febrero 2009
  • enero 2009
  • diciembre 2008
  • noviembre 2008
  • octubre 2008
  • septiembre 2008
  • agosto 2008
  • julio 2008
  • junio 2008
  • mayo 2008
  • abril 2008
  • marzo 2008
  • febrero 2008
  • enero 2008
  • diciembre 2007
  • noviembre 2007
  • octubre 2007
  • septiembre 2007
  • agosto 2007
  • julio 2007
  • junio 2007
  • mayo 2007
  • abril 2007
  • marzo 2007
  • febrero 2007
  • enero 2007
  • diciembre 2006
  • noviembre 2006
  • octubre 2006
  • septiembre 2006
  • agosto 2006
  • julio 2006
  • junio 2006
  • mayo 2006
About This Site

A cras tincidunt, ut tellus et. Gravida scel ipsum sed iaculis, nunc non nam. Placerat sed phase llus, purus purus elit.

Archives Widget
  • January 2010
  • December 2009
  • November 2009
  • October 2009
Categories
  • Entertainment
  • Technology
  • Sports & Recreation
  • Jobs & Lifestyle
Search
  • twitter

Powered by WordPress  |  Business Directory by InkThemes.

This site uses cookies: Find out more.