Herramientas de usuario

Herramientas del sitio


highlevel:csharp

C#

Básico

Variables

Como asignar valores con coma flotante:

Float:		7.5f;
Double:		7.5d;
Decimal:	7.5m;

Matrices

Unidimensionales

  • Declaración: int [] Estudiantes;
  • Declaración indicando el tamaño: Estudiantes = new int [100];
  • Declaración e inicialización: int [] Estudiantes = {1,2,3,4};
  • Matriz de un objeto: SQLParametros[] Parametros; Parametros = new SQLParametros[5];

Multidimensionales

  • Declaración de dos dimensiones: int [,] Vertices;
  • Declaración de tres dimensiones int [,,] Vertices;
  • Declaración indicando el tamaño: Vertices = new int [10,24];

Conversiones (casts)

char	VariableChar;
int 	VariableInt;
VariableInt = 9;
VariableChar = (char)VariableInt;

Trabajar con strings

Si en medio de una cadena ponemos estos carácteres de escape….

\t	tabulación
\r	retorno de carro
\v	tabulación vertical
\f	la impresora pasa a la siguiente página
\n	nueva línia
\x	seguido de dos dígitos hexadecimales lo inserta
\u 	carácter unicode (seguido de cuatro dígitos)
\\	muestra la barra \, si sigue de una t, una r, v, f... provocará el efecto de estas.

Para desactivar carácteres especiales sólo habrá que poner una @ antes del string.

string MyString = @”hello \ world!;
string MyString = “c:\\carpeta\\archivo.txtstring MyString = @”c:\carpeta\archivo.txt<code>
Acceder a una posición del string
<code csharp>
string a = “Hola mundo!;
char b = a[3]; //a será igual a “a”

Si quisiesemos introducir en un string parámetros:

int a = 5;
string b = System.String.Format("La variable a, es: {0}", a.ToString());

Otros Métodos:

  • Replace Éste método recibe como parámetros dos strings, y devuelve uno. Lo que hace es coger el string con el que se ha llamado este método y se le pasa una cadena que es la que sustituirá por la segunda en todo el string y el nuevo estring ya sustituido es el que devuelve.
  • Split Se le pasa un char o un vector de chars (por lo tanto irá entre comillas simples, no dobles), y devuelve un vector de strings. Lo que hace es coger el string donde se ha invocado a este método y corta (hacia la derecha) y el resultado lo guarda en un array de strings que será lo que devuelva.

Bucles y condicionales

while() {}
do {} while ();

foreach:
int [] Array;
Aray = new int [5]
Array[0] = 1;
Array[1] = 2;
...
foreach (int ArrayElement in Array)
	System.Console.WriteLine(ArrayElement);

break rompe el bucle. En cambio continue hace, dentro de un bucle, que pase del código que siga y vuelve a seguir con el bucle.

Parámetros especiales en las funciones

Parámetros de salida

Podemos pasar a una función una variable y esta función se encargará de cambiarle el valor, dentro de la función este valor no podrá ser leido.

void Func (out int a) {
	a=3;
}
Main () {
	int b;
	Func(out b);
}

Parámetros de referencia

Al pasar un valor en una variable, la función lo podrá leer y además escribir:

void Func (ref int a) {
	a++;
}
Main () {
	int b;
	Func(ref b);
}

Método System.Console.WriteLine

Para escribir por pantalla variables usamos: System.Console.WriteLine (“{0}, {1}”, X, Y);
Si quisiesemos escribir una variable float o double (Z), para indicar los decimales: System.Console.WriteLine (“{0:#,##}”,Z); Si Z vale 7,3454 esto escribirá: 7,34.

Método Main

Cada aplicación debe contener sólo una función Main dentro de alguna clase. Esta es donde se inicia el programa y debe de ser unmétodo público y estático. Al programa podemos pasarle argumentos desde la línia de comandos (si estos parámetros son (string[] args)) y también podremos devolver valores.
Si un archivo fuese arrastrado con el mouse, desde windows al ejecutable habrá un argumento, éste será la ruta completa del archivo arrastrado.

Programación Orientada a Objetos

Variables y métodos estáticos y constantes

Una variable estática dentro de la aplicación es una variable de la que sólo habrá una copia. Es decir, si tenemos varios objetos de una clase y ésta una propiedad estática, esta propiedad será la misma para todos los objetos de esa clase.
Para cambiar un campo estático de una clase no podremos hacerlo desde un objeto, sino desde el nombre de la clase:

code codigo = new code();
codigo.X = 5;
code.iZ = 4;

La propiedad iZ es una propiedad estática de la clase codigo.
Con las funciones estáticas ocurre lo mismo, sólo hay una copia en memoria para la aplicación, pero estas no pueden usar elementos de la clase que no estén marcados como estáticos (ej. Intentar acceder a una función desde el main. Podemos llamar a métodos estáticos de una clase sin necesidad de que existan objetos de esta), debemos tener en cuenta que a un método estático podremos acceder desde cualquier parte del programa, pero éste sólo podrá usar clases o métodos y variables estáticas.

La sintaxis para una constante es: const <tipo de dato> <nombre> = <valor>;
En una clase lo mejor es que las constantes sean estáticas, así para cada objeto no se carga siempre esa constante y ese valor cada vez, sino sólo una vez.

Clases anidadas

En c# existe la posivilidad de crear clases anidadas dentro de otras.

class Class1 {
	private int i = 5;
	private class Class2 {}
	public class Class3
	{
		public void escribePrivado () {
			Class1 cls = new Class1();
			Console.WriteLine(cls.i.ToString());
		}
	}
}
class Class4 {
	public static void Main () {
		Class1.Class3 cls = new Class1.Class3();
	}
}

Si main estubiese en class1 podríamos acceder a class2.
En class3 podemos ver como podemos acceder a los elementos privados de class1.
Si en class4 hiciesemos otra clase que heredase de class3, ésta no podría acceder a los elementos privados de class1.

Constructores y destructores

Existe la sobrecarga de constructores.
La sintaxis para un constructor de una clase es: public <nombre de la clase> (<parámetros>) { } A los destructores no los podemos llamar mediante código, pero su sintaxis es: ~<nombre de la clase> { }

Propiedad readonly

Cuando a una variable de una clase le indicamos que es de sólo lectura (ej. public readonly int campo), sólo podremos modificar su valor en la declaración de és (si en el ejemplo incluimos algo así ‘= 123;’) o desde el constructor de la clase.

Descriptores de acceso

  • get Con get recogemos el valor de una variable.
  • set Con set asignamos el valor a una variable.
private int iX, iY;
public int X {
	set {
		iX = X;
	}
	get {
		return iX;
	}
}

Indizadores

Una clase en sí también puede devolver un valor de un array:

class Indizadores {
	public int this[int IND] {
		get{
			switch(IND) {
				case 1:
					return 3;
				case 2:
					return 4;
				default:
					return 0;
			}
		}
	}
}

Llamandola así devolverá un 3: Indizadores E = new Indizadores(); Console.Write(E[1]);
Podemos sobrecargar los indizadores mientras estos tengan diferente la lista de parámetros.

Operadores typeof y sizeof

typeof Indica que tipo es una variable dentro de las bibliotecas .NET (ej. int = Sytem.Int32).

Console.WriteLine(APP.a + " {0} y {1} ", typeof(int), typeof(short));

siceof Para usarlo hay que hacerlo con el compilador preparado para código no seguro e indicando en la función que esta es de código no seguro.

Console.WriteLine("The size of short is {0}.", sizeof(int));

Operaciones con clases

Podemos indicar lo que ocurre si sumamos dos de nuestras clases. Para ello debemos crear una función dentro de la clase con estas características:

  1. Que sea pública y estática
  2. Debemos indicar el valor que devuelve
  3. Luego la palabra clave ‘operator’ seguida del operador que usaremos
  4. Los que le pasaremos.

Los operadores son: + (suma), - (resta), ~ (bit a bit), ++ (incremento), – (decremento).
Después hay otros que deben declararse juntos y deben devolver un bool: true y false, > y <, == y !=.
Ejemplos:

class Punto {
	public int x, y, z;
	public Punto () {
		x = 1;
		y = 2;
		z = 3;
	}
}

Cambio de signo:

public static Punto operator - (Punto TmpPunto) {
	TmpPunto.x = -TmpPunto.x;
	TmpPunto.y = -TmpPunto.y;
	TmpPunto.z = -TmpPunto.z;
	return TmpPunto;
}

Llamandolo como sigue nos cambiaría a todas sus variables el signo:

Punto point = new Punto();
point = -point;

Pongamos sumando dos Puntos (que se sumen las variables del primero con las del segundo de forma correspondiente (también podríamos sobrecargar la resta)):

public static Punto operator + (Punto FstPunto, Punto ScdPunto) {
	Punto TmpPunto = new Punto();
	TmpPunto.x = FstPunto.x + ScdPunto.x;
	TmpPunto.y = FstPunto.y + ScdPunto.y;
	TmpPunto.z = FstPunto.z + ScdPunto.z;
	return TmpPunto;
}

Para el true o false debemos codificar los dos, que devuelva un bool y pensarlo del palo “sí, es cierto que es cierto o no es cierto que es cierto” o “cierto… es falso o no es falso”:

public static bool operator true (Punto TmpPunto) {
	if (TmpPunto.x < 0)
		return true;
	else
		return false;
}
public static bool operator false (Punto TmpPunto) {
	if (TmpPunto.x > 0)
		return true;
	else
		return false;
}

Podemos definir qué ocurriría al hacer una conversión:

  • Si es implícita: public static implicit operator int (Punto TmpPunto);
  • Si es explícita: public static explicit operator int (Punto TmpPunto);

Implicita es si a un double le asignamos un float, explicita es si hacemos algo así: VarFloat = (float)VarInt

Herencia

Para indicar que una clase se hereda de otra, en la declaración: class Point3d : Point2d
Si Point2d tiene por ejemplo X e Y y en 3d queremos volver a definir X deberemos hacer: new public int X;
Si tenemos en la clase Point2d un método que escribe por pantalla la X y la Y y no queremos conservar su código para Point3d pero sí el nombre de la función, deberán ser declaradas así:

  • En Point2d: public virtual void Print() {… }
  • En Point3d: public override void Print() {… }

Podremos también llamar, desde Point3d al método Print de Point2d indicando: base.Print();
Podemos definir un método abstracto de la clase, esto es sólo una definición, una indicación a las clases derivadas que han de poner código dentro de ese método, el método codificado luego será también escrito como override:

  • Dentro de una clase base polígono: public abstract int Perimetro();
  • Dentro de la clase cuadrado derivada de polígono: public override int Perimetro() { … }

Podemos indicar (como hacemos con el indicador this) que una variable viene de su clase base así: base.X. O de esta misma forma, en un método override, indicar que antes de continuar con el código se ejecute lo que se ejecuta en su clase base: int i = base.Add(obj);

Aún así la palabra base también nos serviría, en un ejemplo como en el de Point3d para el constructor, si hacemos un constructor así: public Point3d (int X, int Y, int Z) : base (X, Y) Y en el constructor de su clase base (Point2d) tenemos esta definición: public Point2d(int X, int Y). Lo que hace ese : base(X, Y) es llamar al constructor parametrizado de su clase base Point2d con los parámetros X e Y que le han pasado a Point3d.

Podemos sellar una clase para que de esta no se herede código, de esta forma: sealed class Point2d { … }

Puede ocurrir que la base clase tenga un método que nosotros queramos implementar, pero sta no esté implementada como virtual, para sustituir ese método por el nuevo sólo deberemos incluir el modificador new en la declaración de la función de la clase derivada: new public void Print() { … }

Ámbito de una variable según su declaración

  • public: Son visibles dentro de la misma clase, en clases derivadas y en el código donde se crean los objetos.
  • private: Sólo son visibles dentro de la clase donde se definen.
  • protected: Visibles en la clase donde se definen y en las derivadas.
  • internal: Sólo visibles dentro del archivo binario (compilado) donde se han declarado.
  • protected internal: Una clase pública podrá verse desde otro archivo, si no lo es no.

Object en .NET

Todas las clases base creadas en c#, sin que nosotros hayamos indicado una clase base, se derivan de la clase base System.Object. Ésta contiene ciertos métodos que nos pueden ser útiles:

  • Equals(obj): Compara dos objetos y devuelve true si son iguales o false si no. Es virtual, por lo que se puede modificar.
  • GetType(): Proporciona información obre la clase.
  • ToString(): Pasa a cadena el cntenido del objeto.
string MyString;
MyString = 123.ToString();
  • Finalize(): Es virtual y podemos cambiarlo. No hace nada, pero siempre se le llama cuando el objeto es destruido, por lo que podemos usarlo como destructor.
  • MemberwiseClone(): Crea una copia del objeto.

Por lo que un objeto “objeto” es un tipo estándard que podemos usar en cualquier parte, y cada objeto de cualquier sitio podrá ser asignado a una variable objeto “objeto”. Para saber si una variable es de un tipo o de otro usamos el operador is.

public static void CheckType(object o) {
 if (o is string) {
  Console.WriteLine ("o is String");
  string s = (string)o;
  Console.WriteLine (s);
 }
 else if (o is Int32[]) {
  Console.WriteLine ("o is Int32 array");
  Int32[] ins=(Int32[])o;
  for(Int32 i=0;i< ins.Length;i++)
    Console.WriteLine (ins[i]);
 }
 else if (o is Myclass) {
  Console.WriteLine ("o is Myclass");
  Myclass a = (Myclass)o;
  a.doSomething();
 }
}

Control de excepciones

Para controlar los errores de código en C# usamos try y catch. Lo que hacemos es colocar dentro del try (entre corchetes) el código que puede causar una excepción, luego el catch, que será el código que se ejecute si ocurre una excepción.
También podemos usar finally con el código que vendrá después del try (o del catch) por ejemplo en el try abrimos ficheros, en catch si hay algún error que lo diga y en finally cerramos el fichero.
Para capturar la excepción, podemos coger y en el catch, entre paréntesis declarar una variable Exception y luego mostrarla llamando a su propiedad ToString():

catch (Exception exp) {
	System.Console.WriteLine(exp.ToString());
	System.Console.Read();
}

También existen excepciones ya definidas por .NET. Éstas las introducimos en los parentesis del catch (catch (StackOverflowException)) y ese será el codigo para esa excepción, estas excepciones son:

  • OutOfMemoryException, StackOverflowException, IndexOutOfRangeException…

También puede declarar sus propias excepciones o lanzar excepciones simples, de la aplicación, haciendo un new ApplicationException.
Para lanzar una excepción:

throw new ApplicationException();

Espacios de nombre (namespaces)

Los namespaces pueden estar anidados, podemos incluir varios dentro de uno o podemos indicar que un namespace está incluido dentro del otro así: namespace numero.uno {}, uno estaría dentro de número.
Para declarar un objeto de una clase distinta a la nuestra:

namespace.clase objeto = new namespace.clase();

Podemos usar un mismo namespace en varios archivos. De esta forma el contenido de estos archivos quedará incluido dentro del namespace.
Y podemos acortar el nombre de los namespaces usando alias. Por ejemplo:

using sc = System.Console;

Y usarlo así:

sc.WriteLine(“hola”);

Podemos elegir que un using esté sólo en un espacio de nombres colocando este dentro de su contenedor:

Namespace MyCollections {
	using System.Collections;
	...
}

Otros usos de using

Podemos declarar un termino que englobe namespaces:

using Terminal = System.Console;
...
Terminal.WriteLine("Hola!");

using como instrucción

Podemos usar el comando using para crear una variable por un corto perido de tiempo. Cuando hacemos:

using (Form frm = new Form()) {
...
}

Dentro podemos usar frm como una variable normal. Al salir de este se le llamará al método IDisposable.

Enumeraciones

Una enumeración es una especie de estructura con varios elementos, estos elementos reciben una numeración (el primero 0, los que siguen 1, 2, 3…) a no ser que sea cambiada. Por defecto una numeración usa ints, a no ser que este valor también sea cambiado:

public enum Estado {
  abierto,
  cerrado
}	
public enum Estado : short {
  abierto,
  cerrado
}	   
public enum Estado {
  abierto=10,
  cerrado=50
}		 

El identidificador de la enumeración se podrá usar como una variable (en nuestro caso Estado).

class Punto2d {
	public enum Coordenadas {X, Y};
	public Coordenadas cord;
}
class Punto3d {
	static void Main () {	
		Punto2d point = new Punto2d();
		point.cord = Punto2d.Coordenadas.X;
		System.Console.WriteLine(point.cord);
		Console.ReadLine();
	}
}

Dentro de un if:

public enum Modo {Leer, Escribir};
public BinaryFlux (string path, Modo mode) {
	if (mode == Modo.Escribir)
	{ ... }

Podemos usar una enumeración en un swich:

switch (this.varEnum) {
	case enum.Opt:
		break;
}

Interfaces

Una interfaz actúa como una plantilla para una clase. Todos los métodos declarados en esta deberán ser implementados en la clase. Una clase no puede heredar de varias clases pero sí de varias interfaces. La ventaja es que luego, la interface actúa como si fuese una clase de la que podemos definir objetos:

interface a {
	void b ();
}
class Class2 : a {
	public void b() {
		System.Console.WriteLine("entra");
	}
}
class Class3 {}
class Class1 {
	[STAThread]
	static void Main(string[] args) {
		ArrayList list = new ArrayList();
		list.Add(new Class2());
		list.Add(new Class3());
		list.Add(new Class2());
		for (int i=0; i<list.Count; i++){
			a tmp = list[i] as a;
			if(tmp != null)
				tmp.b();
		}
		System.Console.ReadLine();
	}
}

Para crear una propiedad debemos definir si aceptará get o set: int r { get; set; }
Una interface puede heredar de varias interfaces.

Dispose y Destructores

Interfaz IDispose

Al crear una clase que implemente esta interfaz se le añadirá el método “Dispose”, éste método es llamado cuando el recolector de basura elimina el objeto y, por lo tanto, este método debería de eliminar de memoria otros objetos que este contenga.

Método Dispose

Un método Dispose ha de eliminar todos los objetos que tiene, llamando así, a su vez a sus métodos Dispose. Es importante que un método Dispose pueda ser llamado varias veces sin que provoque ningua excepción. Al llevar un parámetro bool, éste nos sirve para indicar si ha sido llamado por el sistema o por nosotros.

Destructores

En .NET si quisiesemos que el recolector de basura hiciese algo justo al eliminar un objeto, deberíamos sobreescribir en este el método Finalize, excepto en C# que tiene su propia sintaxis de destructor. El uso indebido de estos destructores puede ser perjudicial para el programa, ya que el recolector de basura va sondeando los objetos para eliminarlos y si cuando se van a eliminar aun no se ha lanzado el método finalize lo pone en una cola de objetos que lo han de llamar, por lo que el objeto estaría más tiempo rondando en memoria.
El método Finalize debería ser protected, no deberíamos de dar acceso a él a código de fuera de la clase donde esté, y debería usarse como última instancia, ya que es preferible.
En C# no podemos llamar a Finalize, pero sí crear un destructor así:

~nombreClase { ... }

Conceptos del lenguaje

Archivos

Para usar archivos debemos incluir (con using) System.IO.
Antes de nada necesitaremos un objeto FileStream. Crearemos uno nuevo, los parámetros del constructor son:

  1. String dirección del archivo.
  2. El modo de fichero. FileMode.<modo>, los modos son:
    • Create: Creará uno nuevo, si existe lo sobreescribirá.
    • Open: Abrirá el fichero, debe existir.
    • OpenOrCreate: Si no existe lo creará.
    • Truncate: El archivo debe existir, será borrado su contenido.
  3. La forma de acceso al fichero. FileAccess.<modo>, los modos son:
    • Read: Leer.
    • ReadWrite: Leer y escribir.
    • Write: Escribir.

Luego crearemos un BinaryReader o BinaryWriter si lo que queremos es leer o escribir, el parámetro que espera recibir es el FileStream antes creado. Una vez acabemos las operaciones que hayamos hecho deberemos cerrar tanto el BinaryReader o Writer y el FileStream con su método .Close().

FileStream FS = new 
FileStream(Text2.Text, FileMode.OpenOrCreate, FileAccess.ReadWrite);
BinaryWriter BW = new BinaryWriter(FS);
BW.Write(Text1.Text);
BW.Close();
FS.Close();
FileStream FS = new FileStream(Text2.Text, FileMode.Open, FileAccess.Read);
BinaryReader BR = new BinaryReader(FS);
Text1.Text = BR.ReadString();
BR.Close();
FS.Close();		 

Podemos posicionarnos en cualquier lugar del archivo (o del FileStream) mediante el método Seek. Mediante vamos leyendo o escribiendo en un archivo el puntero del archivo va abanzando.

  • Si queremos ir al principio haremos: <objeto filestream>.Seek (0, SeekOrigin.Begin);
  • Ir al cuarto primer byte: <objeto filestream>.Seek (4, SeekOrigin.Begin);
  • Al penultimo byte: <objeto filestream>.Seek (-2, SeekOrigin.End);
  • Al enterior respecto a la posición del puntero donde tamos: <objeto filestream>.Seek (-1, SeekOrigin.Current);

Ya que la enumeración SeekOrigin admite estos cuatro elementos.

Conversiones

  • A integer: variable int = System.Convert.ToInt16 (variable);
  • A IP: System.Net.IPAddress var = System.Net.IPAddress.Parse(var string);

Operador as

Es un molde para la conversión de tipos, pero si existe un error en la conversión, proporciona el valor null en vez de producir una excepción. Más formalmente, una expresión de la forma: expression as type, que equivale a: expression is type ? (type)expression : (type)null

Delegados

Un delegado permite al programador encapsular un método dentro de un objeto delegado. Lo que significa que un delegado sería una especie de variable que contiene una “función”. El delegado se usa como si fuese un tipo de datos, y su declaración se hace de la siguiente manera:
<tipo de variable (pública, privada…)> delegate <lo que devuelve> nombre <lo que recibe>.

Imaginemos un espacio de nombres y declaramos un delegado:

public delegate void Callback (string s);

Este delegado no devuelve nada pero recibe un string, sólo podremos asignarlo a una función con estas mismas características. Imaginemos esta:

private static void WriteResult (string s) {
	Console.WriteLine(s);
}

Para asignarlo se hace como si fuese un objeto con el nombre del delegado pero que se le pasa un parámetro que es el nombre de la función:

Callback WR = new Callback(WriteResult);

Imaginemos que el programador realiza una función que recoge todos los objetos de una lista, si estos objetos tienen un color igual al color indicado se llamará a una función que realizará la rutina correspondiente, el programador no tiene por qué tener ni idea de lo que hace esta rutina, sólo ha de saber que se le pasa un string y que no devuelve nada (que es una función del tipo “callback”):

public void SearchData(string SearchColor, Callback Proc) {
	foreach (Objeto obj in this.Lista) {
		if (obj.Color == SearchColor) {
			Proc(obj.Name);
		}
	}
}

Para llamar a esta última función, como vemos debemos pasarle un string y un Callback, es decir, el color y el Callback antes definido:

D.SearchData("Negro",WR);

También podemos sumar y restar delegados. Esto es, al sumar un delegado, que se llame una función y luego a otra, al restar, quitamos de un delegado una función sumada.
Por ejemplo imaginemos que tenemos dos funciones Hola y Adios, las dos no devuelven nada pero reciben un string. Lo que hacen es decir “Hola + string”, o “Adios + string”. Vamos a imaginar que tenemos un delegado para estas.
Declaramos cuatro delegados de los nuestros: a, b, c y d. a = new Delegado (Hola), b = new Delegado (Adios), c = a + b, d = c – a.
Luego llamamos a los delegados: a(“A”), b(“B”), c(“C”), d(“D”).
Esto nos devolverá:

El a:		Hola A.
El b:		Adios B.
El c:		Hola C. Adios C.
El d:		Adios D.

Añadir un nuevo evento a una clase

Lo primero que debemos hacer es declarar un delegado, este parece ser una platilla para los eventos. Lo hemos puesto como global dentro del espacio de nombres “Project2”: public delegate void <nombre> ( <parámetros> ). Dentro de la definición de la clase donde añadiremos este evento lo definimos como tal, public event <nombre del delegado> <nombre del evento>. A partir de ahora, cuando llamemos a este “evento” desde esta clase debemos hacerlo como si de una función se tratase, pero con los parámetros indicados al delegado. Aún así debemos indicar en algún lugar que función corresponde a ese evento, es decir, la que se ejecutará cuando se llame a este.

namespace Project2 {
	public delegate void EvNumPar (object Originador, int Num);
	class Motor {
		public event EvNumPar DarNumero;
		public void Ejecuta() {
			for (int i = 0; i <= 100; i++) {
				if (DarNumero != null)
					DarNumero(this, i);
			}
		}
	}
	class MainClass {
		public static void NumPar (object Originador, int Num) {
			if (Num % 2 == 0)
				Console.WriteLine("Número Par {0} ", Num);
		}
		public static void Main () {
			Motor motor = new Motor();
			motor.DarNumero += new EvNumPar(NumPar);
			motor.Ejecuta();
			Console.ReadLine();
		}
	}
}

.NET Framework, en sus eventos, indica que l delegado para un evento debe aceptar dos parámetros: el orígen del objeto y la información adicional: (object sender, EventArgs e). Este no ha de devolver nada.
Podemos quitar delegados del evento con el operador -=.
En el siguiente código vemos una clase que hereda de la clase ArrayList, ésta tiene un evento llamado “Adding” que al añadir un objeto es llamado (mediante la función onAdding añadida en el método Add) si este evento apunta a alguna función, es decir, que no es nula.

namespace Eventos {
	using System.Collections;
	class Lista : ArrayList {
		public event EventHandler Adding;
		protected virtual void OnAdding (EventArgs e) {
			if (Adding != null)
				Adding(this, e);
		}
		public override int Add(object value) {
			int i = base.Add(value);
			OnAdding(EventArgs.Empty);
			return i;
		}
	}
}
namespace Prueba {
	using Eventos;
	class MainClass {
		static Eventos.Lista list;
		static void Muestra () {
			foreach (object obj in list) 
				Console.WriteLine(obj.ToString());
		}
		static void List1_Change(object sender, EventArgs e) {
			Console.WriteLine("\tSe ha llamado al método Change");
		}
		public static void Main () {
			list = new Lista();
			list.Adding += new EventHandler(List1_Change);
			list.Add("perro");
			list.Add("gato");
			Muestra();
			Console.ReadLine();
		}
	}
}

Referencias y valores

Cuando se crea un objeto de una clase (obj = new cls()), realmente este lo que hace es crear un espacio reservado en memoria para una estructura de datos (no me refiero a una struct) que corresponda a la clase y convierte la “variable” que representa ese objeto en una especie de puntero en memoria a él, por lo que cuando hacemos una igualación de objetos (cls obj1 = obj) esta es otro puntero al mismo espacio de memoria. Por lo que si varía una varía la otra.
Por lo que si hacemos una asignación así: B = A; y luego B = C;, B y C harían referencia a un mismo objeto y A a otro. Para trabajar con tipos de valores, que si hacemos una igualación el valor se iguala pero si cambia uno no cambia el otro debemos de trabajar con estructuras.
Trabajar por referencias nos permite pasar, por ejemplo, un objeto en un evento para que sea manipulado y luego, al finalizar el evento, realizar las acciones adecuadas según lo que se haya tocado.

Atributos

Los atributos se añaden a la declaración de una porción de código y complementan a esta, se pueden aplicar a un ensamblado, un módulo, una clase, estructura, enumeración, constructor, método, campo, bla bla bla…. Los encontramos antes de cada uno de estos elementos entre corchetes.
Un ejemplo sería el atributo obsolete, a éste se le pasa un string y un bool. El bool indicará si el compilador encontrará un waring (false) o un error (true). Y el string la explicación que dará.

[Obsolete("Este método está obsoleto", true)]
static void Antiguo () {}

En verdad el atributo Obsole se llama ObsoleteAttribute, y el compilador de C# lo primero que hará será buscar en sus referencias un atributo llamado ‘Obsolete’, si no lo encontrase añadiría a Obsolete (o el atributo en cuestión) la palabra Attribute y la volvería a buscar.
Para crear un atributo, debemos crear una clase que herede de System.Attribute, a partir de ahí ya tenemos nuestro nuevo atributo llamado como la clase.
Para definir que código puede acceder a nuestro atributo añadiremos a este un atributo en la definición de la clase, este es AttributeUsage y recibe miembros de una enumeración llamada AttributeTargets que incluye todo el tipo de código al que es aplicable un atributo en general. Por ejemplo [AttributeUsage(AttributeTargets.Class)] haría que ese atributo sólo fuese aplicable a clases. Podemos indicar más código accesible mediante el operador OR ( | ).
Un código puede recibir un atributo repetidas veces, por lo que el código recibiría varios valores, por defecto esto no está permitido, pero si el atributo AttributeUsage también recibe como parámetro AllowMultiple=true lo permitirá. Esto…

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
class HelpAttribute : System.Attribute

… permite esto….

[Help("Este me mola")]
[Help("Este no me mola")]
static void Antiguo ()

Si una clase hereda de otra clase, esta última clase contendrá los atributos de su clase base?? La respuesta es sí si el parámetro Inherit de AtributeUsage es true, si es false no heredará.
No haremos que el atributo reciba más de un parámetro en el constructor (ya que los parámetros los indicamos en el constructor), sino mediante propiedades. Por ejemplo en un constructor podríamos inicializar las propiedades del atributo, pero estas (si son públicas), serán sobrescritas si el programador las define:

class HelpAttribute : System.Attribute {
	protected string Descripcion;
	public string Uso;
	public HelpAttribute (string Explicacion) {
		this.Descripcion = Explicacion;
		this.Uso = "nulo";
	}
}

Con este atributo, si al usarlo hacemos: [Help(“mola”)] quedará Description como: “mola” y Uso como “nulo”. Pero si hiciesemos: [Help(“mola”, Uso=”muy útil”)] quedará Description como: “mola” y Uso como “muy útil”.
Antes de incluir el atributo al código podemos indicar que ese atributo es para ese código en concreto, es decir, podemos asignar un atributo al valor devuelto, pero este atributo lo incluimos sobre el método, cómo indicamos? Pues podemos poner unos “identificadores” delante del atributo, estos dirán a qué influyen: assembly, module, type, method, return, param…. Ej: [method:Help(“mola”)].

Punteros

El uso de punteros en C# es código no seguro, y para usar éste código debemos indicar al compilador que use una compilación específica: Proyecto → Propiedades → Generar → Permitir bloques de código no seguro. Además deberemos incluir en la declaración de la función la palara unsafe.
El uso de punteros es muy parecido al de C/C++:

public static unsafe void Main () {
	int AInteger = 123;
	int *Puntero = &AInteger;
	Console.WriteLine(*Puntero);
	*Puntero = 150;
	Console.WriteLine(AInteger);
	Console.ReadLine();
}

Usamos *<variable puntero> para declarar y acceder a un puntero, y &<variable> para acceder a una variable no puntero. El código anterior devolverá los valores 123 y 150.

Para acceder a las propiedades de una estructura, ya que a un objeto no podemos hacerle punteros ( ¬_¬ …ya…), hacemos un puntero del tipo de la estructura y luego asignamos al puntero la variable de la estructura con un & delante. Ahora, para acceder a sus propiedades usaremos: guión y mayor: ->
Para hacer un puntero a un array primero deberemos reservar espacio en memoria con stackalloc y el tipo:

int[] Array = {1,2,3,4,5};
int * puntero = stackalloc int [5];
for (int i = 0; i < 5; i++)
	puntero[i] = Array[i];

Puede ser que C# mueva de la memoria alguna variable, y por tanto un puntero a esa variable se volvería inservible y hasta erroneo. Para asegurarnos de que esto no ocurre usaremos fixed:

int[] Array = {1,2,3,4,5};
fixed(int *puntero = Array) {
	for (int i = 0; i < 5; i++)
		puntero[i] = i;
}

De esta forma, aunque la variable puntero no exista, Array no será movida.
Podemos saber el tamaño de una variable con el operador sizeof(<tipo>).

Notas

  • Una nueva línea al escribir texto se consigue con: Environment.NewLine

Tips

  • Crear un objeto de un solo uso: new Thread(new ThreadStart(Thread2)).Start();
  • Asignar a un bool una expresión: bool bPar = (iNum % 2 == 0);
  • Definir dos objetos del mismo tipo: Image tal = image.fromfile(“asjdf”), tal2 = image.fromfile(“sakfjdl”);
highlevel/csharp.txt · Última modificación: 2020/05/09 09:25 (editor externo)