Regreso a las bases: Memoria (Parte 2)

Lo que sigue es una traducción de una sección del ebook gratuito Foundations of Programming de Karl Seguin.

Apuntadores

Para muchos desarrolladores, aprender sobre apuntadores en la escuela fue una experiencia dolorosa. Representan la verdaderamente real indirección que existe entre código y hardware. Muchos más desarrolladores nunca tuvieron la experiencia de aprender sobre ellos  – saltaron directamente a programar en un lenguaje que no los expone directamente. La verdad sin embargo es que cualquiera que diga que C# o Java son lenguajes sin apuntadores es simplemente un error. Como los apuntadores son el mecanismo con el cual todos los lenguajes almacenan valores en el heap, es más bien tonto no entender como son usados.

Los apuntadores representan el nexus del modelo de memoria de un sistema – esto es, los apuntadores son el mecanismo donde el stack y el heap trabajan juntos para proveer el subsistema de memoria requerido por su programa. Como discutimos anteriormente, cuando instancia un objeto new, .NET asigna un bloque de memoria al heap y regresa un apuntador al inicio de este bloque de memoria. Esto es todo lo que un apuntador es: la dirección de inicio para el bloque de memoria que contiene un objeto. La dirección no es nada más que un número único, generalmente representado en formato hexadecimal. Por lo tanto, un apuntador no es nada más que un número único que le dice a .NET donde está el objeto mismo en memoria. Esta indirección es transparente en Java o .NET, pero no en C o C++ donde se puede manipular la dirección de memoria directamente con un apuntador aritmético. En C o C++ se puede tomar un apuntador y agregar 1 a él, y así arbitrariamente cambiar a donde está apuntando (y seguramente hacer tronar el programa debido a esto).

Donde se pone interesante es donde el apuntador está realmente almacenado. Ellos en realidad siguen las mismas reglas descritas arriba: como enteros son almacenados en el stack – al menos, claro, que ellos formen parte de una referencia a un objeto y entonces estarán en el heap con el resto del objeto. Puede no ser claro aún, pero esto significa que ultimadamente, todos los objetos heap están enraizados al stack (posiblemente a través de numerosos niveles de referencias). Veamos primero este ejemplo simple:

static void Main(string[] args)

{

  int x = 5;

  string y = “codebetter.com“;

}

Del código de arriba, terminaremos con 2 valores en el stack, el entero 5 y el apuntador a nuestra cadena, así como también precisamente el valor en el heap. Aquí una representación gráfica:

Cuando salimos de nuestra function main (olvidémonos del hecho de que el programa se parará), nuestro stack liberará todos los valores locales, lo que significa que tanto el valor de x como de y se perderán. Esto es significativo porque la memoria asignada en el heap todavía contiene nuestra cadena, pero hemos perdido toda referencia a ella (no hay algún apuntador apuntándola). En C o C++ esto resulta en una fuga de memoria – sin una referencia a nuestra dirección en el heap no podemos liberarla de la memoria). En C# o Java, nuestro confiable recolector de basura detectará el objeto sin referencia y lo liberará.

Veremos ejemplos más complejos, que aparte de tener más flechas apuntando, es básicamente el mismo.

public class Empleado

{

  private int _empleadoId;

  private Empleado _gerente;

  public int EmpleadoId

  {

    get { return _empleadoId; }

    set { _empleadoId = value; }

  }

  public Empleado Gerente

  {

    get { return _gerente; }

    set { _gerente = value; }

  } 

  public Empleado(int empleadoId)

  {

    _empleadoId = empleadoId;

  }

}

public class Prueba

{

  private Empleado _subordinado;

  void HacerAlgo()

  {

    Empleado jefe = new Empleado(1);
    _subordinado = new Empleado(2);

    _subordinado.Gerente = _jefe;

  }

}

Interesantemente, cuando salimos de nuestro método, la variable jefe se liberará del stack, pero el subordinado, que está definido por el alcance padre, no. Esto significa que el recolector de basura no tendrá nada que limpiar porque los dos valores del heap seguirán siendo referenciados (uno directamente del stack, y el otro indirectamente del stack a través del objeto referenciado.

Como puede ver, los apuntadores definitivamente juegan una parte importante tanto en C# como en VB.NET. Como el apuntador aritmético no está disponible en ninguno de estos lenguajes, los apuntadores son grandemente simplificados y con suerte fácilmente entendidos.

 

Regreso a las bases: Memoria

Lo que sigue es una traducción de una sección del ebook gratuito Foundations of Programming de Karl Seguin.

Regreso a las bases: Memoria

Por más que se intente, los lenguajes modernos de programación no pueden abstraer completamente los aspectos fundamentales de los sistemas computacionales. Por ejemplo, podemos asumir que usted se ha encontrado con las siguientes excepciones .NET: NullReferneceException, OutOfMemoryException, StackOverflowException y ThreadAbortException. Tan importante como es para desarrolladores adopter varios patrones y técnicas de alto nivel, es igualmente importante comprender el ecosistema en el cual su programa se ejecuta. Mirando por encima de las capas proveídas por el compilar de C# (o VB.NET), el CLR y el sistema operativo, nos encontramos con la memoria. Todos los programas hacen uso extensivo de la memoria del sistema e interaccionan con ella en maravillas maneras, es difícil ser un buen programador sin comprender esta interacción fundamental.
Mucha de la confusión sobre la memoria nace del hecho de que tanto C# y VB.NET son lenguajes administrados y que el CLR provee la recolección automática de basura. Esto ha causado que muchos desarrolladores asuman erróneamente que no necesitan preocuparse por la memoria.

Asignación de Memoria

En .NET, como en muchos otros lenguajes, cada variable que se defina está almacenada en el stack  o en el heap . Estos son dos espacios separados asignados en la memoria de sistema que sirven un propósito distinto, aunque complementario. Lo que va donde está predeterminado: valores de tipos van en el stack, mientras que la referencia a tipos va en el heap. En otras palabras, todos los tipos de sistema, como char, int, long, byte, enum y cualquier estructura (ya sean definidas por.NET o por usted) van en el stack. La única excepción a esta regla son los valores de tipos que pertenecen a referencias de tipos – por ejemplo la propiedad Id de una clase User va en el heap junto con la instancia de la clase User misma.

El Stack

Aunque estamos acostumbrados al mágico colector de basura, los valores en el stack son automáticamente administrados aún en un mundo sin colector de basura (como en C). Esto es porque cuando sea que entramos a un nuevo alcance (como un método o una sentencia If) los valores son empujados al stack y cuando salen del stack los valores son liberados. Esta es la razón por la que un stack es sinónimo a LIFO – last-in first-out (último en entrar primero en salir). Puede pensarlo en este modo: cuando se crea un nuevo alcance, por ejemplo un método, un marcador es puesto en el stack y los valores son añadidos como se necesiten. Cuando se deja ese alcance, todos los valores son liberados incluyendo el marcador del método. Esto funciona en cualquier nivel de anidado.
Hasta que veamos la interacción entre el heap y el stack, la única manera real de meterse en problemas con el stack es con StackOverflowException. Esto significa que ha usado todo el espacio disponible del stack. 99.9% del tiempo, esto indica una llamada recursiva interminable (una función que se llama a sí misma ad infinitum). En teoría esto puede ser causado por un muy muy mal diseño de sistema, aunque nunca he visto una llamada  no recursiva usando todo el espacio del stack.

El Heap

La asignación de memoria en el heap no es tan simple como el stack. La mayoría de la asignación de memoria basada en el heap ocurre cuando creamos un objeto new. El compilador averigua cuanta memoria necesitaremos (lo cual no es tan difícil, aún con objetos con referencias anidadas), toma un apropiado montón de memoria y regresa el apuntador a la memoria asignada (más acerca de esto en un momento). El ejemplo más sencillo es una cadena, si cada carácter en una cadena toma 2 bytes, y creamos una nueva cadena con el valor de “Hola Mundo”, entonces el CLR necesitará asignar 22 bytes (11×2) más cualquier adicional necesitado.
Hablando de cadenas, seguramente ha oído que las cadenas son inmutables – esto es, una vez que ha sido declarada una cadena y asignado un valor, si se modifica esa cadena (cambiando el valor o concatenando otra cadena a ella), entonces una nueva cadena se crea. Esto realmente puede tener implicaciones de rendimiento negativas, y por ello la recomendación general es usar un StringBuilder para cualquier manipulación de cadenas significativa. La verdad es que cualquier objeto almacenado en el heap es inmutable con respecto a la asignación de tamaño, y cualquier cambio en el tamaño subyacente requerirá una nueva asignación. El StringBuilder, junto con algunas colecciones, parcialmente pueden sacar la vuelta a esto usando buffers internos. Una vez que el buffer se llena, la misma reasignación ocurre y algún tipo de algoritmo de crecimiento es usado para determinar el nuevo tamaño (el más simple siendo antiguoTamaño * 2). Siempre que sea posible es buena idea especificar la capacidad inicial de dichos objetos para evitar este tipo de reasignación (el constructor para tanto el StringBuilder y el  ArrayList (entre muchas otras colecciones) le permiten especificar capacidad inicial).
Recolectar basura del heap es una tarea no trivial. A diferencia del stack donde el último alcance puede simplemente liberarlo, los objetos del heap no son locales a un determinado alcance. En lugar de ello, la mayoría son referencias profundamente anidadas de otros objetos referenciados. En lenguajes como en C, cuando un programador causa que la memoria sea asignada al heap, debe asegurarse también de remover del heap cuando ha terminado con él. En lenguajes administrados, el motor en tiempo de ejecución se encarga de limpiar los recursos (.NET usa un Recolector de Basura Generacional que está brevemente descrito en la Wikipedia).
Hay muchos incidentes horribles que pueden molestar a los desarrolladores mientras trabajan con el heap. Fugas de memoria no solo son posibles sino muy comunes, la fragmentación de memoria puede causar todo tipo de caos, y varios problemas de rendimiento pueden generarse gracias a comportamiento de asignación extraño o interacción con código sin administrar (lo cual .NET hace mucho debajo del agua).

Screencasts sobre ADO.NET Entity Framework con C#

En esta serie de screencasts vemos como hacer cosas sencillas con ADO.NET Entity Framework, para un primer contacto con esta tecnología.

Introducción

Northwind y pubs

Northwind y pubs:
Para saber como tener instaladas las bases de datos de prueba Northwind y pubs, proveídas por Microsoft

Usando C# en ASP.NET con ADO.NET Entity Framework

Usando C# en ASP.NET con ADO.NET Entity Framework

Un ejemplo rápido para ver como es el contexto de uso del ADO.NET Entity Framework

 

Tareas específicas

Búsqueda en ADO.NET Entity Framework con C#

Búsqueda en ADO.NET Entity Framework con C#
Paso inicial para el resto de las tareas

Alta de registros con ADO.NET Entity Framework con C#

Alta de registros con ADO.NET Entity Framework con C#
Como agregar registros

Baja de registros con ADO.NET Entity Framework con C#


Baja de registros con ADO.NET Entity Framework con C#


Como eliminar registros

Cambios a registros en ADO.NET Entity Framework con C#

Cambios a registros en ADO.NET Entity Framework con C#

Como hacer modificaciones

No abre http://localhost Visual Studio

Hace unos días unos compañeros de la universidad me advirtieron de un problema que les ocurría en común a máquinas donde tuvieran Visual Studio 2008 y particularmente en sistema operativo Windows Vista. Se trata de que al intentar correr alguna aplicación ASP.NET desde el entorno de desarrollo les salía Internet Explorer con la infame “Internet Explorer cannot display the webpage” y pues, estaban sin poder continuar con sus trabajos.

iecannot

Al principio me imaginaba que era algo que tenía que ver con el archivo de hosts y después de investigar un poco más, me dieron la razón.

Lo que debes hacer es usar un editor de texto (como Notepad o Notepad++) y abrir desde el directorio C:\Windows\System32\Drivers\Etc\ un archivo que se llama hosts (sin extensión) y remover la línea que dice algo de localhost. Te puede aparecer así:

::1             localhost

o así

127.0.0.1       www.localhost.com

Ahora nada más graba este archivo otra vez, cuidando que no se anexe la extensión txt (y si se anexa, se la quitas). Parece medio absurdo, pero puede ser que si te ocurre este problema, quizás sea por esto.

Comunidades Virtuales

De la Facultad de Contaduría, Administración e Informática de la UABC, me invitaron a dar una plática acerca de Comunidades Virtuales en la cual les comenté acerca de como los foros, sitios de redes sociales y otros conforman lo que a todas luces es comunidad que aunque virtuales, llevan un gran sentido de pertenencia y participación, además como la Comunidad .NET Tijuana en particular hace uso de estas herramientas para complementar esta comunidad presencial. Aquí algunas fotos y la presentación misma:

 

 

 

Por cierto, un agradecimiento a Sergio Quijada y su equipo por la invitación.

ADO.NET Entity Framework y llaves foráneas

Es algo muy normal trabajar con tablas que están relacionadas con otras por medio de llaves foráneas. Si has usado ADO.NET Framework para hacer algunas operaciones con la BD y para mostrar datos en una página ASP.NET, quizás te hayas encontrado con el dilema de mostrar los campos relacionados de la tabla externa a la que estás mostrando.

Por ejemplo, tomando como ejemplo la clásica base de datos de Northwind, y teniendo las tablas de Products y Categories, y si definimos nuestro modelo de Entidades de esta forma:

modelo

Podemos mostrar en un GridView el contenido de Products haciendo la referencia correspondiente:

        NorthwindModel.NorthwindEntities ne = 
            new NorthwindModel.NorthwindEntities();

        var products = from p in ne.Products
                       select p;

        grdProductos.DataSource = products;
        grdProductos.DataBind();

 grid

Pero ¿qué tal si quiero mostrar en el mismo GridView la descripción de la categoría? Basta incluir en la declaración del campo la propiedad Include para que se pueda cargar ese dato.

        NorthwindModel.NorthwindEntities ne = 
            new NorthwindModel.NorthwindEntities();

        var products = from p in ne.Products
                       .Include("Categories")
                       select p;

        grdProductos.DataSource = products;
        grdProductos.DataBind();

Y con esto tenemos acceso a los campos de la segunda tabla para de esa manera mostrar como fuente de datos de alguna columna del grid algún campo. En este caso, con un Template Field hacemos un Bind usando simplemente Categories.Description que sería la manera de ligar desde Products a ese campo:

    <asp:TemplateField HeaderText="Category">
        <EditItemTemplate>
            <asp:TextBox ID="TextBox1" runat="server" 
                Text='<%# Bind("Categories.Description") %>'>
                </asp:TextBox>
        </EditItemTemplate>
        <ItemTemplate>
            <asp:Label ID="Label1" runat="server" 
                Text='<%# Bind("Categories.Description") %>'>
            </asp:Label>
        </ItemTemplate>
    </asp:TemplateField>

gridconcategoria

Por supuesto que en lugar de esto pudiéramos usar Vistas desde el servidor SQL Server con todos los campos que ocupemos, pero esta sería una de las formas de resolver como mostrar los campos en tablas mediante llaves foráneas usando código de ADO.NET Entity Framework.

Formato de fecha en Gridview

No está de más recordar que cuando tienes un GridView con un campo tipo fecha, puedes definir el formato de la cadena a lo que tú gustes, en lugar de los valores por default de fecha completa:

fechafull

Para definir el formato de salida de la cadena en campos ligados, es sencillo, usando la propiedad DataFormatString:

 

<asp:BoundField DataField="Fecha" DataFormatString="{0:d}" />

 

Pero ¿qué pasa si tenemos TemplateFields, sencillo, usando la misma función Bind donde especificamos en el enlace al campo de datos para agregarle el formato que necesitamos:

 

<asp:TemplateField HeaderText="Fecha">
  <EditItemTemplate>
    <asp:TextBox ID="TextBox1" runat="server" 
             Text='<%# Bind("Fecha","{0:d}") %>'>
    </asp:TextBox>
  </EditItemTemplate>
  <ItemTemplate>
    <asp:Label ID="Label1" runat="server" 
              Text='<%# Bind("Fecha","{0:d}") %>'>
     </asp:Label>
  </ItemTemplate>
</asp:TemplateField>

 

De esta manera podemos mostrar a nuestra conveniencia el formato de los datos que mostramos en un gridview:

fecha

Algunas funciones Javascript útiles para ASP.NET (en C#)

Nunca está de más conocer algunas funciones útiles para hacer más efectiva la programación web en ASP.NET, entre los que uso bastante son:

Ventana de Confirmación (Messagebox del lado del cliente)

Muy útil para pedirle confirmación al usuario cuando se va a hacer algún proceso del cual se puede arrepentir, por ejemplo, pedirle confirmación para borrar algún registro. En el botón donde se va a agregar la lógica para hacerlo, se le agrega el detalle para abrir una ventana y pedir confirmación. En el evento Load de la página es un buen lugar para hacerlo:

    protected void Page_Load(object sender, EventArgs e)
{
btnEliminar.Attributes.Add("onclick",
"return confirm('¿Eliminar registro?');"
);
}

Si el usuario presiona Ok, se ejecutará el código para borrar el registro, si no, es como si nunca hubiera tocado el botón.

Mostrar una ventana con tamaño determinado

A veces necesitamos mostrar una ventana extra independiente del área de trabajo del usuario para información, algún proceso pequeño o algo que para demostrar atención no puedes abrir en un nuevo tab o en otra ventana del navegador. De esta forma incluso podemos dimensionar la ventana emergente con el tamaño adecuado para nuestros fines.

    protected void Page_Load(object sender, EventArgs e)
{
btnNueva.Attributes.Add("OnClick",
"window.open('Nueva.aspx',null,'height=220,width=480');"
);
}

A tomar en cuenta es que el usuario puede tener bloqueadas las ventanas emergentes, así que una buena idea es mostrar en algún lado de la aplicación que desactive para tu sitio este bloqueo.

Cerrar una ventana

Para casos como el anterior donde después de abrir una ventana donde se espera generar trabajo pero no se puede confiar o resultaría muy engorroso para el usuario cerrar manualmente la ventana, conviene usar este método. En el ejemplo está en el evento Click de un botón pero bien puede figurar después de terminado alguna función.

    protected void btnCerrar_Click(object sender, EventArgs e)
{
Response.Write("<script>window.close();</script>");
}

Estas son algunas funciones donde es necesario hacer trabajo del lado del cliente para determinado comportamiento, por lo mismo no es para todas las situaciones y eventos posibles. Lo único es preguntarse si son cosas que no se pueden hacer del lado del servidor o que serían más rápidas y sencillas que se hicieran del lado del cliente.