¡Esta es una revisión vieja del documento!
En c# 2 se nos permite, cuando vamos a asignar el valor de un delegado hacerlo en la misma declaración:
Button btn = new Button(); btn.Click += delegate(object sender, System.EventArgs e) { MessageBox.Show("alsl"); }; this.Controls.Add(btn);
private delegate void llamaFunc(); ... llamaFunc func = delegate() { MessageBox.Show("asdfl"); }; func();
Sirven para contener métodos independientes.
En C# 2 puedes partir una clase en varios archivos de código o en varias partes, para ello has de hacer la declaración de la clase en cada archivo donde la vayas a codificar pero indicando en todas sus definiciones la palabra partial delante.
Dichas definiciones no pueden contradecirse entre ellas, esto es, por ejemplo, indicar varias clases base o distintos modificadores de acceso.
La herencia o modificadores de acceso sólo es necesario declararlos en una sola declaración de clase.
No sólo las clases pueden ser parciales, también estructuras e interfaces.
Si las definiciones de clases parciales se hacen en distintos ensamblados, la del ensamblado que referencie al otro sobreescribirá a la anterior, al menos en el ámbito de esta.
partial class Class1
Ahora podemos declarar tipos de valor nulos, antes no podíamos, por ejemplo asignar a un int un valor null, ahora sí, para ello declaramos un int que pueda ser nulo, para ello colocamos un ? detrás del tipo de variable: int? x = null;
Ahora esta x es nula, no la podemos asignar a otro integer ya que daría error, para poder asignarla tenemos la opción del valor por defecto, este se lo indicamos tras dos interrogantes después de la variable al asignarla: int j = x ?? 3; Esto significa que si x es nula j valdrá 3.
Podemos saber si una variable en este momento es nula mediante una propiedad que contiene, esta es: HasValue, devuelve true si tiene valor, es decir, si no es nula.
Una variable declarada como posible nula es un atajo de System.Nullable<T>, donde T es el tipo genérico, es decir, podríamos declarar :
System.Nullable<int> f = null;
int?[] i = new int?[3];Seguimos sin poder usar el conversor as para los tipos nulos.
Si estamos codificando sobre namespaces del sistema y se nos ha liado el acceder a clases del sistema o simplemente porque queremos acceder a los namespaces raíz que tenemos activos, podremos colocar la clausula “global” seguida de dos puntos: global::System;
Los ensamblados que declaramos como amigos de un ensamblado B permiten ver de B todas sus clases internals.
Para declarar un ensamblado amigo necesitamos saber la clave pública y declararlo así:
[assembly:InternalsVisibleTo ("AssemblyB, PublicKeyToken=32ab4ba45e0a69a1")]
Un ensamblado A que tiene una declaración de “amistad” en B, y B de C, por defecto A no será “amigo” de C.
Este concepto es el de dar accesivilidad parcial a una propiedad, esto es, que su escritura (set) y su lectura (get) tengan distinta accesivilidad. Podemos dar accesivilidad únicamente a una de las dos formas de acceso, siempre y cuando esa accesivilidad sea más restrictiva que la accesivilidad de la propiedad.
public int PropPruebaA { protected set { this.iA = value; } get { return this.iA; } }
La accesivilidad al hacer override de una propiedad ha de ser la misma que la de la base.
Las propiedades heredadas de interfaces no pueden contener accesivilidad.
En C# 1.x nos encontrábamos con un serio problema al crear arrays dinámicos, y es que estos sólo aceptaban 'objects'. En C# 2.0 existen los generics, arrays dinámicos a los que se les define un tipo que aceptarán.
Definir un ArrayList genérico de tipo string:
System.Collections.Generic.List<string> lst = new List<string>(); lst.Add("Juan"); lst.Add("Pedro"); Console.WriteLine(lst[1]);
Definir una HashTable genérica que tiene como clave un string y como valor otro string:ç
System.Collections.Generic.Dictionary<string, string> ht = new Dictionary<string,string>(); ht.Add("A", "Primera letra"); ht.Add("Z", "Última letra"); ht["a"] = "Primera letra minuscula"; ht["Z"] = "Última letra mayuscula"; Console.WriteLine(ht["a"]); Console.WriteLine(ht["Z"]); Console.WriteLine(ht["A"]);
Los Dictionary tienen una clave y un valor, para recorrer una hashtable debemos saber de qué tipo es la clave y de qué tipo el valor que contiene, a partir de ahí podemos sacarlos fácilmente.
foreach (System.Collections.Generic.KeyValuePair<string, string> valor in ht) Console.WriteLine(string.Concat(valor.Key, ": ", valor.Value));
Podemos definir un método genérico, siguiendo la siguiente sintaxis: void PruebaMetodosGenericos<T>(T var). Este método recibe una variable genérica, T, es como un object ya que no tiene un tipo definido. Es más, funciona como tal, no podemos hacer con ella, de momento, nada distinto a lo que haríamos con un object.
Para dar un formato más a ese objeto lo que hay que hacerle es aplicarle unas restricciones, estas son una especie de estilos. Tipos de restricciones:
Para indicar las restricciones lo haremos con la palabra where después de la declaración del método y pondremos el nombre de la restricción, luego dos puntos y la definiremos con las clausulas antes indicadas separadas por comas, si queremos definir otra restricción volveremos a introducir where, nombre de restricción, dos puntos y clausulas. Luego empezaríamos a codificar el método.
Ejemplo de una definición de método generic con multiples argumentos:
void Mostrar <T1, T2> (T1 var1, T2 var2, string str) where T1 : IComparable where T2 : class, IComparable
Podemos crear nuestras propias clases genéricas, únicamente debemos definir en la declaración de la clase el\los tipo\s que permitirá indicando, de la misma forma que en un método, las restricciones de dicho tipo:
class ListaPersonas<T> where T : Persona { private System.Collections.Generic.List<T> Lista = new List<T>(); private int iCount = 0; public int Count { get { return this.iCount; } } public void Add(T persona) { this.Lista.Add(persona); } public System.Collections.IEnumerator GetEnumerator() { foreach (Persona p in Lista) yield return p; } }
En C# 1.x, para realizar una clase que pueda ser leida por la instrucción foreach es necesario que esta herede de las interfaces IEnumerable e IEnumerator.
Gracias al comando yield de C# 2.0 es más fácil recorrer una clase que funcione como colección, únicamente debemos crear un método llamado GetEnumerator que devuelva un IEnumerator. Ahora podemos devolver cualquier tipo de objeto varias veces indicando, antes del return, el comando yield:
string[] m_Days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" }; public System.Collections.IEnumerator GetEnumerator() { foreach (string day in m_Days) yield return day; }
yield también puede ser llamada así: yield break, que cortaría la continuidad del foreach. Si este código lo encontramos en una clase llamada DaysOfTheWeek podemos usarlo de la siguiente forma:
DaysOfTheWeek dw = new DaysOfTheWeek(); foreach (string s in dw) Console.WriteLine(s);
En modo unsafe, C# 2.0 nos permite crear arrays de tamaño fijo en las estructuras, esto es, si una estructura tiene un array no tiene por qué ocupar en memoria un tamaño invariable por culpa de un array sin dimensión concreta, esto ocurría en C# 1.x, que debías declarar un array en una estructura así: public int[] NumDNI; Ahora, gracias al comando fixed podemos hacer esto: public fixed int[8] NumDNI;. (Pero sólo en modo unsafe). Los buffers de tamaño fijo deberán de ser arrays de una sola dimensión.
Esta clase nos permite medir el tiempo de un proceso.
System.Diagnostics.Stopwatch swatch = new System.Diagnostics.Stopwatch(); swatch.Start(); for (int i = 0; i < int.MaxValue; i++) ; swatch.Stop(); MessageBox.Show(swatch.Elapsed.ToString());
Añade un panel al formulario con forma de tabla, de esta forma es muy fácil, en modo diseño, montar una buena organización de controles. Los controles, al añadirse lo hacen en celdas, además adquieren algunas nuevas propiedades:
ToolStripPanel nos permite indicar en qué posiciones podrán colocarse los “strip controls”. Estos strip controls son el ToolStrip (lo que sería la toolbar), el StatusStrip (la barra de estados)…
Podemos definir propiedades de las clases de forma más sencilla si no tienen necesidad de contener lógica, para ello colocamos { get; set; } justo después de la declaración de la propiedad. Si quisieramos hacer una propiedad readonly sólo tendríamos que indicar el set como private.
class LightweightCustomer { public double TotalPurchases { get; set; } public string Name { get; private set; } // read-only public int CustomerID { get; private set; } // read-only }
Podemos crear una lista de elementos con propiedades autoimplementables de la forma siguiente:
class Person { public string firstName { get; set; } public int age { get; set; } } class Program { static void Main(string[] args) { List<Person> people = new List<Person> { new Person { firstName = "Juan", age = 33 }, new Person { firstName = "Pedro", age = 14 }, new Person { firstName = "MaryCury", age = 25} }; } }
Son expresiones que pueden ser asignadas como funciones simples. Una expresión lambda consiste en:
=>.(int x) => x + 1; // Explícitamente tipado (x,y) => x * y; // Implícitamente tipado x => x + 1; // Implícitamente tipado, un sólo parámetro
Por ejemplo la expresión lambda x ⇒ x + 1 significa “toma x como argumento y devuelve el resultado de la operación con x”.
Las expresiones lambda pueden ser asignadas a delegados para darles cuerpo. Por ejemplo el delegado TResult System.Func<T, TResult>define una función que recibe como parámetro un tipo T y devuelve un tipo TResult.
Cuando el compilador asigna la expresión lambda a una variable delegado se usa el tipo indicado para determinar el tipo de los parámetros de la función lambda.
En el siguiente vemos como utilizar una variable delegado mediante métodos anónimos y mediante una expresión lambda:
Func<string, string> convert = delegate(string s) { return s.ToUpper();}; Func<string, string> convertLambda = s => s.ToUpper(); string name = "Dakota"; Console.WriteLine(convert(name)); Console.WriteLine(convertLambda(name));
Las listas utilizan en varios métodos este delegado para realizar operaciones sobre los datos que contienen, aquí es donde pueden ser explotadas este tipo de expresiones:
IEnumerable<Person> p = people.Where(x => x.age < 30); // Retorna una lista con los elementos Person que tengan una age menor de 30 people.Average(x => x.age); // Retorna la media de age de todos los elementos dentro de people
LINQ da de forma nativa expresiones para describir operanciones sobre datos que no han sido modelados de forma orientada a objetos (por ejemplo, crear expresiones que busquen en un array de elementos ubicado en memoria) a partir de cierto patrón. También puede ser extensible a otros campos (including LINQ to SQL, LINQ to Dataset, LINQ to Entities, LINQ to XML, o LINQ to Objects) siempre y cuando los elementos que se buscan extienden IEnumerable.