Este post viene como consecuencia de los comentarios del gran @eiximenis, en este otro var en c# si, o var no. Lo que ha hecho Eduard es elevar con la segunda respuesta la entrada al destino de los elegidos:).
Referente al titulo “Los hermanos de Simba” es porque la fiera se va a comer al gatito.
La primera de las respuestas, he de reconocer que estuve pensando en escribirla en el primero de los post, pero eso de la vida que te hace perezoso y en un momento decides cortar.
Eduard nos dice lo siguiente.
Efectivamente, como bien dices en var el tipo del lvalue es siempre igual al del rvalue (eso es var precisamente). Pero, en según que ocasiones, eso no nos sirve… Un ejemplo rápido…
myListbox.Items.Add(new Foo());
foreach (var fooItem in myListbox.Items) { … }
En este caso, al usar var, fooItem es un Object, pero si yo sé que en la lista hay solo Foo, probablemente querré que fooItem sea de tipo Foo. Este es solo un ejemplo. A eso me refería yo 😉
Por que ocurre esto? Porque en el ejemplo Eduard está utilizando un ObjectCollection, claro a alguien le pareció demasiado tedioso con la aparición de generic reescribir Windows Form y demás y lo que se hizo fue dejar la cosa como estaba e implementar IEnumerable<T>, todos sabemos que para utilizar el magnifico foreach alguien debe de implementar en sus clases como mínimo IEnumerable y digo como mínimo, puesto que lo lógico es utilizar IEnumerable<T>.
Vamos con un ejemplo, partimos de la siguiente clase.
1: public class Foo
2: {
3:
4: }
Si yo escribo el siguiente conjunto de instrucciones
1: var foo = new Foo();
2: foreach (var item in foo)
3: {
4:
5: }
Recibo un error de compilación donde me dice lo siguiente.
La instrucción foreach no puede funcionar en variables de tipo ‘Namespace.Foo’ porque ‘Namespace.Foo’ no contiene ninguna definición pública para ‘GetEnumerator’.
Sí Implementamos IEnumerable en “Foo”.
1: public class Foo:System.Collections.IEnumerable
2: {
3:
4: public System.Collections.IEnumerator GetEnumerator()
5: {
6: throw new NotImplementedException();
7: }
8: }
A partir de este momento yo puedo ejecutar foreach in “Foo”.
Pero claro, con esto no contestamos a Eduard, para ello nos debemos fijar en el valor devuelto por GetEnumerator un IEnumerator cuya definición es la siguiente.
1: [ComVisible(true), Guid("496B0ABF-CDEE-11d3-88E8-00902754C43A")]
2: public interface IEnumerator
3: {
4: bool MoveNext();
5: object Current { get; }
6: void Reset();
7: }
8:
Si nos fijamos Current es object, por eso es por lo que a “var” se le asigna un “object”, con lo cual “var” si está haciendo lo que dice hacer.
Claro porque “var” si funciona con List<T>,etc,etc, pues sencillo porque estos han subido un nivel y han implementado IEnumerable<T> y por tanto IEnumerator<T> donde la propiedad Current devuelve T.
1: public interface IEnumerator<T> : IDisposable, IEnumerator
2: {
3: // Properties
4: T Current { get; }
5: }
Pero como se que me va a decir que porque yo puede hacer lo siguiente.
1: private void Metodo()
2: {
3: var Lista = new System.Collections.ArrayList();
4: Lista.Add(new Foo());
5: foreach (Foo item in Lista)
6: {
7:
8: }
9: }
Le vamos a contestar, con un simple “una cosa es lo que tus ojos ven y otra bien distinta lo que hay”.
Un poquito de IL:).
1: .method private hidebysig instance void Metodo() cil managed
2: {
3: .maxstack 2
4: .locals init (
5: [0] class [mscorlib]System.Collections.ArrayList Lista,
6: [1] class WindowsFormsApplication24.Foo item,
7: [2] class [mscorlib]System.Collections.IEnumerator CS$5$0000,
8: [3] bool CS$4$0001,
9: [4] class [mscorlib]System.IDisposable CS$0$0002)
10: L_0000: nop
11: L_0001: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()
12: L_0006: stloc.0
13: L_0007: ldloc.0
14: L_0008: newobj instance void WindowsFormsApplication24.Foo::.ctor()
15: L_000d: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
16: L_0012: pop
17: L_0013: nop
18: L_0014: ldloc.0
19: L_0015: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.ArrayList::GetEnumerator()
20: L_001a: stloc.2
21: L_001b: br.s L_002b
22: L_001d: ldloc.2
23: L_001e: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
24: L_0023: castclass WindowsFormsApplication24.Foo
25: L_0028: stloc.1
26: L_0029: nop
27: L_002a: nop
28: L_002b: ldloc.2
29: L_002c: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
30: L_0031: stloc.3
31: L_0032: ldloc.3
32: L_0033: brtrue.s L_001d
33: L_0035: leave.s L_0051
34: L_0037: ldloc.2
35: L_0038: isinst [mscorlib]System.IDisposable
36: L_003d: stloc.s CS$0$0002
37: L_003f: ldloc.s CS$0$0002
38: L_0041: ldnull
39: L_0042: ceq
40: L_0044: stloc.3
41: L_0045: ldloc.3
42: L_0046: brtrue.s L_0050
43: L_0048: ldloc.s CS$0$0002
44: L_004a: callvirt instance void [mscorlib]System.IDisposable::Dispose()
45: L_004f: nop
46: L_0050: endfinally
47: L_0051: nop
48: L_0052: ret
49: .try L_001b to L_0037 finally handler L_0037 to L_0051
50: }
y más concretamente nos vamos a centrar en estás dos instrucciones
1: L_001e: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
2: L_0023: castclass WindowsFormsApplication24.Foo
Hombre un OpCodes.Castclass. Lo más bonito de este link no es otra cosa que la siguiente frase.
“Intenta convertir un objeto pasado por referencia en la clase especificada”
Efectivamente “intenta”, con lo cual si no puede, sorpresa y nos llevaremos como premio, un magnifico “InvalidCastException” y para ello simplemente creamos una clase Foo1 y la agregamos al ArrayList.
1: private void Metodo()
2: {
3: var Lista = new System.Collections.ArrayList();
4: Lista.Add(new Foo());
5: Lista.Add(new Foo1());
6: foreach (Foo item in Lista)
7: {
8:
9: }
10: }
Lo que es evidente, que var funciona, puesto que es imposible saber que es lo que ArrayList tiene. Solo sabemos que tiene object y por tanto “var” es “object”.
Pero se te olvido una cosa OfType<T>() y entonces “var” si que funciona perfectamente, aunque que quede claro que no aconsejo el uso de esta instrucción en este caso, puesto que nos comemos un elemento. Lo que si aconsejo es que nos olvidemos para siempre si puede ser de las colecciones no genericas, aunque algunas veces es imposible y más cuando se trata de collecction ligadas a WindowsForm,WPF,Silverlight, que bastante mal se llevan con el uso de Generic.
En definitiva para que “var” funcione correctamente en este caso basta con escribir lo siguiente.
1: private void Metodo()
2: {
3: var Lista = new System.Collections.ArrayList();
4: Lista.Add(new Foo());
5: Lista.Add(new Foo1());
6: foreach (var item in Lista.OfType<Foo>())
7: {
8:
9: }
10: }
Aunque como dije anteriormente el segundo elemento nos lo comemos, de esta forma veo que yo si utilizaría “var” siempre en un foreach.
Cuando me refería a no abusar, me refería, aunque se puede, a utilizar este tipo de cosas.
1: var i = ((Func<int, int, int>)((x, y) => x + y))(10,10);
Esto me recuerda a un antiguo compañero que cuando sumaba enteros escribía “var z = 0x3e8 + 0x3e8;” que si hombre que sí que z = 2000 ó z = 1000+ 1000;
Aclarado el primer comentario, vamos con el segundo que ese si que está lleno de mala leche:).
La pregunta es… Suponiendo que lo anterior compilase, esa otra línea debería hacerlo?
var r = (x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17 => x1+x17)(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17);
Repito: si la sentencia anterior compilase, esa debería compilar también? Sí? No? Tic, tac…
PD: Si, admito que si alguien crea una lambda con 17 parámetros es para darle una colleja 😛
La respuesta es No y os explico, para ello nos debemos trasladar a la primera aparición de Func y Action en el Framework 3.5. El número maximo de parametros permitidos eran 4.
Si avanzamos en el tiempo y analizamos esto mismo a partir del Framework 4.0 nos encontramos con que los parametros permitidos son 16, fijaos en la maldad de poner 17:).
tic,tac…. Pero no conformándonos con esto vamos a demostrarle a Eduard que si compilo.
Alguien me impide a mí definir un Func y un Action con 17 parámetros o con 50, pues no otra cosa es que directamente te pueden llamar “ñapas”:).
1: namespace System
2: {
3: public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16,in T17, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16,T17 arg17);
4:
5: }
Y ahora vamos a hacer que compile utilizando “var”.
1: var i = ((Func<int, int, int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int>)
2: ((x1, x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17) =>
3: x1+x2+x1+x4+x5+x6+x7+x8+x9+x10+x11+x12+x13+x14+x15+x16+x17))
4: (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17);
5: Console.Write(i);
El resultado obtenido que es pues sencillo 151, pero claro no debéis de utilizar esta técnica para sumar una secuencia de números correlativa, mas vale que veáis este video.
http://www.youtube.com/watch?feature=player_embedded&list=PLB33C2EAFBCB22655&v=9gv2YyRvdB0
Tic,Tac……
Saludos,