C# Básico: Objetos y referencias

La verdad es que ahora hacía bastantes meses que no publicaba nada de la serie “C# básico”. En esta serie pongo posts sobre temas básicos del lenguaje. No es un libro por fascículos, ni un tutorial al uso puesto que los posts no tienen orden en concreto y nacen a partir de inquietudes que observo (mayoritariamente en los foros, pero también por correos que recibo). Todos los posts de esta serie los podéis ver aquí.

En el post de hoy quiero hablar de la diferencia entre objetos y referencias ya que observo que no siempre está clara. Gente que entiende los conceptos básicos de herencia parece liarse en este punto. Muchas veces es un tema pasado rápidamente en muchos libros y tutoriales. Y es que, la verdad, es un tema muy sencillo… 😉

MiClase miObjeto = new MiClase();

¿Qué hace este código? En muchos sitios leerás que lo que hace es crear un objeto de la clase MiClase. Eso es cierto, pero describe lo que hace lo que hay a la derecha del símbolo de asignación. Qué hace el código que está a la izquierda? Pues lo que hace es declarar una referencia de tipo MiClase. Otra palabra que se usa muchas veces en lugar de referencia es variable aunque no son técnicamente lo mismo (hay variables que no son referencias y las referencias pueden asignarse a otros elementos que no llamamos usualmente variables como p.ej. los parámetros a una función).

Las referencias contienen objetos. Yo prefiero decir que las referencias apuntan a objetos (aunque esta palabra parece como “maldita”, sin duda por culpa de los punteros) para que quede claro que un mismo objeto puede estar contenido en (apuntado por) más de una referencia.

El tipo de una referencia

Todas las refencias tienen un tipo. Este tipo es único e inmutable durante toda la vida de la referencia. El tipo de una referencia determina que objetos puede contener dicha referencia. En concreto:

  1. Objetos del mismo tipo. Es decir, una referencia de tipo MiClase puede contener objetos de la clase MiClase.
  2. Objetos de una clase derivada de la clase del tipo de la referencia. Si la referencia es de tipo MiClase puede contener objetos de cualquier clase derivada de MiClase.
  3. Objetos de cualquier clase que implemente el tipo de la referencia. Eso aplica sólo si el tipo de la referencia es una interfaz. En este caso la referencia puede contener un objeto de cualquier clase que implemente dicha interfaz.

Todas las clases en .NET derivan de Object. Por lo tanto, según el punto (2) una referencia de tipo Object, puede contener cualquier objeto de cualquier clase:

Object objeto = new CualquierClase();

¿Condiciona alguna cosa más el tipo de la referencia? Pues sí: el tipo de la referencia condiciona como vemos al objeto contenido en dicha referencia. Es decir, la referencia es como un disfraz para el objeto. Le permite “ocultar su tipo real” y mostrarse como el “tipo de la referencia”.

P.ej. dado el siguiente código:

class MiClase
{
public void Foo() {}
}

class MiClaseDerivada : MiClase
{
public void Bar() {}
}

MiClase c1 = new MiClaseDerivada();
MiClaseDerivada c2 = new MiClaseDerivada();

Podemos ver como MiClase define un método (Foo) y MiClaseDerivada que deriva de MiClase añade el método Bar. Luego c1 es una referencia de tipo MiClase que contiene un objeto de MiClaseDerivada (puede según el punto 2 anterior). Y c2 es una referencia de tipo MiClaseDerivada que contiene un objeto de MiClaseDerivada (posible según el punto 1 anterior). Entonces tenemos que:

c1.Foo();   // Ok.
c1.Bar(); // No compila.
c2.Foo(); // Ok.
c2.Bar(); // Ok.

La llamada c1.Bar() no compila. ¿Por que? Pues simplemente porque la referencia es de tipo MiClase. Y MiClase no tiene ningún método Bar. Da igual que el objeto contenido por dicha referencia sea de tipo MiClaseDerivada, que sí que tiene el método Bar. El compilador no se fija en los tipos de los objetos. Se fija en los tipos de las referencias.

Objetos compartidos

Como hemos dicho antes un mismo objeto puede estar contenido por más de una referencia:

MiClaseDerivada c1 = new MiClaseDerivada();
MiClase c2 = c1;

En este punto tenemos dos referencias. Pero un sólo objeto. Es decir, c1 y c2 contienen el mismo objeto, que es un objeto de tipo MiClaseDerivada. Si accedo al objeto a través de c1 lo veo como un objeto de tipo MiClaseDerivada (ya que este es el tipo de c1). Por otro lado si accedo al objeto a través de c2 lo veo como un objeto de tipo MiClase (al ser este el tipo de c2). Por lo tanto c1.Bar() es correcto y c2.Bar() no compila.

Pero insisto: son el mismo objeto. Observad el siguiente código:

class Program
{
public static void Main()
{
MiClaseDerivada c1 = new MiClaseDerivada();
MiClase c2 = c1;
c1.Incrementar();
c2.Incrementar();
Console.WriteLine("El valor de c1 es:" + c1.Valor);
Console.WriteLine("El valor de c2 es:" + c2.Valor);
}
}


class MiClase
{
private int valor;

public int Valor { get { return this.valor; } }
public void Incrementar()
{
valor++;
}
}

class MiClaseDerivada : MiClase
{
// Código
}

¿Cual es la salida por pantalla de dicho código? Pensadlo con detenimiento. Pues  la siguiente:

El valor de c1 es:2

El valor de c2 es:2

Eso es debido porque c1 y c2 contienen el mismo objeto. Por lo tanto inicialmente tenemos que el valor de dicho objeto es 0. Al llamar a c1.Incrementar() el valor pasa a ser 1. Y al llamar a c2.Incrementar(), el valor pasa a ser 2, ya que el objeto que contiene c2 es el mismo que el objeto que contiene c1.

Así pues recordadlo siempre: Asignar una referencia a otra NO crea un nuevo objeto. Simplemente hace que la referencia contenida a la izquierda de la asignación contenga EL MISMO objeto que la referencia situada a la derecha.

Comparando objetos y referencias.

De nuevo la forma más fácil es verlo con un código de ejemplo:

class Program
{
public static void Main()
{
Persona p1 = new Persona();
p1.Nombre = "Pepito";
p1.Edad = 20;
Persona p2 = p1;
Persona p3 = new Persona();
p3.Nombre = "Pepito";
p3.Edad = 20;
bool b = p2 == p1;
bool b2 = p3 == p2;
}
}

class Persona
{
public string Nombre { get; set; }
public int Edad { get; set; }
}

¿Cual es el valor de b y b2?

  • b vale true porque p1 y p2 contienen el mismo objeto
  • b2 vale false porque p3 y p2 contienen objetos distintos. Da igual que los dos objetos sean del mismo tipo y sean idénticos. En este caso son dos Personas idénticas: mismo nombre y edad. Pero el operador == compara referencias, no objetos.

Así pues recuerda: El operador == al comparar referencias devuelve true sólo si las dos referencias contienen el mismo objeto. En caso contrario devuelve false (aunque las dos referencias apunten a dos objetos idénticos).

Nota: Este comportamiento del operador == puede modificarse para que compare el valor de los objetos en lugar de indicar si las dos referencias contienen el mismo objeto. P.ej. la clase string tienen modificado dicho operador para comparar el valor de las cadenas. Esto queda fuera del alcance de este post.

La comparación de objetos (es decir, determinar si dos objetos son idénticos pese a ser dos objetos distintos) es algo que por norma general depende de la clase. P.ej. dos Personas serán iguales si tienen el mismo nombre y edad. Dos cadenas serán iguales si contienen los mismos carácteres. Depende de cada clase determinar que significa que dos objetos son iguales. Para estandarizar un poco la comparación de objetos, en .NET tenemos el método Equals. Dicho método está definido en la clase Object y por lo tanto, por herencia, existe en todas las clases. Si quiero indicarle al framework como comparar dos objetos de tipo Persona puedo añadir a la clase Persona el siguiente código:

public override bool Equals(object obj)
{
if (obj is Persona)
{
Persona otro = (Persona) obj;
return otro.Edad == Edad &&
otro.Nombre == Nombre;
}
return false;
}

Y para comparar los objetos, debo llamar a Equals en lugar del operador ==

bool b2 = p3.Equals(p2);

Conversiones (castings)

En el código del método Equals anterior hay el siguiente código:

Persona otro = (Persona)obj;

El código (Persona) es lo que se llama casting. El casting lo que hace es cambiar el tipo de una referencia. Es decir en el caso anterior obj era una referencia de tipo object (los parámetros también pueden ser referencias). Recordad que las referencias de tipo object pueden contener cualquier objeto. Pero yo quiero acceder a Nombre y Edad que son campos definidos en la clase Persona y por ello necesito una referencia de tipo Persona que me contenga el mismo objeto que la referencia obj.

Si directamente probáramos:

Persona otro = obj;

Dicho código no compila. ¿Porque? Pues porque otro es una referencia de tipo Persona y por lo tanto solo puede contener:

  1. Un objeto de tipo Persona
  2. Un objeto de cualquier clase que derive de Persona

Pero obj es una referencia de tipo object y puede contener un objeto de tipo object o bien un objeto de cualquier clase que derive de object… es decir, de cualquier clase. Imaginad, entonces:

object obj = new Perro();
Persona otro = obj;

Es evidente que el objeto contenido por obj es un perro y no una persona. Si el código de la segunda línea compilase estaríamos viendo un perro como una persona y bueno… se supone que no se puede, no? Por eso, como el compilador no puede garantizar que el objeto (recordad que el compilador no se fija en objetos) contenido por la referencia obj sea de un tipo válido para la referencia otro, se cura en salud y no nos deja compilar el código.

Pero… tu no eres el compilador y tu sí te fijas en los objetos. ¿Qué pasa en aquellos casos en que tu sabes que el objeto contenido por la referencia obj es de un tipo válido para la referencia Persona? Pues que debes decírselo al compilador. ¿Cómo? Usando el casting:

Persona otro = (Persona)obj;

Aquí le estás diciendo al compilador: Quiero que la referencia otro contenga el mismo objeto que la referencia obj y tranquilo, no te quejes porque yo te digo que el objeto es de tipo Persona. Con el casting el compilador te cree y te deja hacer la asignación.

Eh… que te crea el compilador no significa que te crea el CLR. El CLR no se fía ni de su madre, así que si tu haces:

object perro = new Perro();
Persona persona = (Persona)perro;

El compilador no se quejará, pero cuando ejecutes, vas a recibir una hermosa InvalidCastException. El CLR sí que se fija en los objetos, como tu 🙂

Ah! Y aunque el compilador no se fije en objetos… no lo insultes, eh? No intentes algo como:

Perro perro = new Perro();
Persona persona = (Persona)perro;

Eso no compila. La razón es porque no es necesario fijarse en los objetos para ver que una referencia de tipo Persona nunca podrá contener el mismo objeto que una referencia de tipo Perro: Persona y Perro no tienen nada en común. El compilador puede no fijarse en los objetos, pero no es tonto!

Un saludo!

Binding de colecciones en ASP.NET MVC (iii)

Bueno… vamos a seguir viendo el tema de binding de colecciones con ASP.NET MVC. En los dos posts anteriores hemos visto:

En este post vamos a ver como enlazar una colección de N elementos, de los cuales sólo nos llegan un determinado número, pero queremos fácilmente saber cuales son. Es decir, si nos llega sólo el primer elemento, el segundo y el octavo, recibir una lista con los ocho elementos, todos ellos a “null” (o un valor por defecto) excepto los informados (el primero, el segundo y el octavo en nuestro caso).

Si usamos el DefaultModelBinder esto no pasa: en los posts anteriores hemos visto como en el mejor de los casos (usando el parámetro index), recibimos sólo una colección con los tres elementos, y debemos usar ModelState.Keys para saber cuales son los índices reales informados. Es decir, si la vista sólo nos informa del primer, segundo y octavo elementos en el controlador recibimos una colección de tres elementos (los tres informados). Para saber que el tercer elemento (p.ej.) de dicha colección se corresponde al octavo índice real debemos usar ModelState.Keys. Vamos a ver ahora como podemos hacerlo para recibir, en este caso, una colección con los ocho elementos. De estos ocho, tan sólo el primer, el segundo y el octavo tendrán valor (el resto, un valor por defecto).

La solución es simple, y pasa por crearnos un Custom Model Binder 🙂 Crear un model binder propio parece muy complejo, pero se trata de implementar una interfaz con un solo método (BindModel). Sí, si miras el código del DefaultModelBinder te parecerá enorme y complejo, pero piensa que el DefaultModelBinder está pensado para enlazar cualquier cosa, y nosotros vamos a hacer un model binder preparado para enlazar sólamente colecciones (IEnumerable<T> en nuestro caso).

Así pues, vamos a hacer este custom model binder, especializado en colecciones. Vamos a imitar en todo al Default Model Binder, excepto en que nosotros vamos a devolver una colección con el tamaño real (no solo con los elementos informados).

Os pongo primero el código del model binder y lo discutimos (por supuesto, si queréis preguntar algo concreto sobre el código, adelante!):

public class CollectionBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{

var model = CreateModel(bindingContext) as IList;
var prefix = bindingContext.ModelName;
var indexesKey = bindingContext.FallbackToEmptyPrefix ?
bindingContext.ValueProvider.GetValue("index") :
bindingContext.ValueProvider.GetValue(string.Format("{0}.index", prefix));
var indexes = indexesKey == null ? AllIndexes() : EnumerableFromIndexes(indexesKey.RawValue as string[]);
var genericType = GetGenericTypeOfModel(bindingContext);


foreach (var index in indexes)
{
var value = bindingContext.FallbackToEmptyPrefix ?
bindingContext.ValueProvider.GetValue(string.Format("[{0}]", index)) :
bindingContext.ValueProvider.GetValue(string.Format("{0}[{1}]", prefix, index));

if (value != null)
{
var valueConverted = Convert.ChangeType(value.AttemptedValue, genericType);
model.Add(valueConverted);
}
else
{
if (indexesKey == null) break;
else
{
model.Add(genericType.IsValueType
? Activator.CreateInstance(genericType)
: null);
}
}
}


return model;
}

private object CreateModel(ModelBindingContext bindingContext)
{
var genericType = GetGenericTypeOfModel(bindingContext);
var listOfTType = typeof(List<>).MakeGenericType(new Type[] { genericType });
return Activator.CreateInstance(listOfTType);
}

private Type GetGenericTypeOfModel(ModelBindingContext bindingContext)
{
var type = bindingContext.ModelType;
var genericTypes = type.GetGenericArguments();
return genericTypes.FirstOrDefault();
}

private IEnumerable<int> AllIndexes()
{
for (int i = 0; i < Int32.MaxValue; i++)
{
yield return i;
}
}


private IEnumerable<int> EnumerableFromIndexes(string[] indexesToUse)
{
if (indexesToUse != null)
{
foreach (var token in indexesToUse)
{
yield return Int32.Parse(token);
}
}
}
}

Como funciona el siguiente código? Pues nuestro collection binder hace lo siguiente:

  1. Crea un objeto para representar el modelo. Dicho objeto será siempre una List<T>, siendo T el parámetro genérico del IEnumerable del modelo.
  2. Mira si existe el parámetro index. Si dicho parámetro existe, lo usa para saber los indices reales de la colección.  Es decir, si indexes vale “0,1,2,3,4,5” (p.ej.) nuestro model binder va a devolver siempre una colección de 6 elementos (del 0 al 5) con independencia de los elementos reales informados en la vista. Esto es para imitar lo que hace el DefaultModelBinder y que vimos en el post anterior.
  3. Busca en los valueproviders los valores para todos los índices. Si el parámetro “index” no existía, todos los indices son literalmente “todos” (de 0 a Int32.MaxValue-1). Si el parámetro index no existe nos paramos cuando falta un elemento (porque si no, siempre devolveríamos una colección de Int32.MaxValue elementos!). Por su parte si el parametr index existe, iteramos sólo sobre sus valores, y si el valor no existe, lo añadimos al modelo con el valor por defecto del tipo genérico. Es decir, si index vale “0,1,2,3,4,5” y la vista no nos informa del valor del índice 3, pondremos el valor por defecto en el índice 3 y continuaremos hasta llegar a 5.

El uso de los value providers para obtener los valores nos independiza de si dichos valores vienen por GET, POST o lo que sea. De esta manera el Model Binder es independiente de la request de http.

Este CollectionBinder está preparado para trabajar con cualquier tipo de IEnumerable. Para usarlo, debemos registrarlo en global.asax:

ModelBinders.Binders[typeof(IEnumerable<string>)] = new CollectionBinder();

Con esto, lo hemos registrado para que los IEnumerable<string> se enlacen usando nuestro model binder!

¿Lo probamos? Para ello nos creamos un modelo:

public class FooModel
{
public string Name { get; set; }
public int Age { get; set; }
public IEnumerable<string> Data { get; set; }
}

Y luego un controlador con un método para recibir un FooModel:

public ActionResult Index()
{
var model = new FooModel();
model.Age = 10;
model.Name = "Nombre";
model.Data = new List<string> {"cero", "uno", "dos", "tres", "cuatro"};
return View(model);
}
[HttpPost]
public ActionResult Index(FooModel model )
{
int i = 0;
// Codigo...
}

Vamos ahora a hacer una vista para editar nuestro FooModel:

@using BindingColecciones3.Models
@model FooModel
<!DOCTYPE html>
<html>
<head>
<title>title</title>
</head>
<body>
<div>
@using (Html.BeginForm())
{
<div>
@Html.LabelFor(x => x.Name)
@Html.EditorFor(x => x.Name)
<br />
@Html.LabelFor(x => x.Age)
@Html.EditorFor(x => x.Age)
</div>
<ul>
@for (var idx = 0; idx < Model.Data.Count(); idx++)
{
<li>Checkbox #@idx:
<input type="checkbox" name="Data[@idx]" value="@Model.Data.Skip(idx).First()"/>
<input type="hidden" name="Data.index" value="@idx"/>
</li>
}
</ul>
<input type="submit" />
}
</div>
</body>
</html>

Fijaos en como creamos las checkboxes: El atributo name de cada checkbox es Data[0], Data[1], Data[2]… Eso es porque Data es el nombre de la propiedad IEnumerable<string> de nuestro modelo. El atributo value de cada checkbox será la cadena que se enlazará en el modelo. Si p.ej. sólo marcamos la tercera checkbox (cuyo value es “dos”, eso es lo que recibiremos en el controlador:

image

Fijaos que, a diferencia del CustomModelBinder, lo que recibimos ahora es una colección de 6 elementos (0-5) y sabemos exactamente cual era la única checkbox marcada. Esa misma vista, pero usando el DefaultModelBinder para enlazar los datos, devolvería lo siguiente al controlador (tal y como vimos en el post anterior):

image

Y deberíamos usar ModelState.Keys para saber que este “dos” es el valor de la tercera checkbox marcada.

Recordad que esto ocurre porque los navegadores no envían valores para una checkbox NO marcada. Es decir, en HTML las checkboxes no tienen el valor de true o false. Tienen sólo el valor que ponga en su value si están marcadas o no existen si no están marcadas.

Y finalmente una consideración sobre el código de este CollectionModelBinder: Tiene algunas limitaciones, alguna que otra cosa que se podría mejorar, alguna incongruencia (sobretodo en la gestión del parámetro index) y cosas que se le podrían añadir… Os dejo que vayáas pensando cuales… y alguna de ellas las veremos en un siguiente post, que por hoy, es suficiente, no? 😉

Un saludo!