Programación · n.º 8 entre los lenguajes más usados

Aprende C# desde cero

El lenguaje de .NET: aplicaciones de Windows, webs, servicios y videojuegos con Unity.

  • 15 lecciones
  • 19 ejercicios interactivos
  • 25 conceptos clave
  • 27,1 % lo usa*

Qué es C# y para qué sirve

C# es el lenguaje principal de .NET, la plataforma gratuita y multiplataforma de Microsoft. Con él se hacen aplicaciones de Windows, webs y APIs con ASP.NET Core, servicios en la nube y videojuegos con el motor Unity. Su sintaxis se parece a la de Java.

  • Aplicaciones de Windows y de escritorio
  • Webs y APIs con ASP.NET Core
  • Videojuegos con Unity

Para empezar: Instala el SDK de .NET, crea un proyecto con dotnet new console y ejecútalo con dotnet run.

Nivel 1 Básico

Lección 1 · Básico

Tu primer programa

Con el SDK de .NET instalado, dotnet new console crea un proyecto y dotnet run lo compila y lo ejecuta. El archivo Program.cs puede empezar directamente con instrucciones, sin declarar clases (instrucciones de nivel superior).

Console.WriteLine() escribe una línea en la consola.

Ejemplo práctico
// En la terminal:
// dotnet new console -o MiApp
// cd MiApp
// dotnet run

// Program.cs
Console.WriteLine("¡Hola, mundo!");
Console.WriteLine(2 + 3); // 5

Ejercicio

Completa la instrucción que escribe una línea.

Console.("Hola");
Ver la solución

Respuesta: WriteLine. Console.WriteLine() escribe el texto y salta de línea.

Lección 2 · Básico

Variables, tipos e interpolación

C# es de tipado estático: int, double, decimal (para dinero, sin errores de redondeo binario), bool y string. Con var el compilador deduce el tipo.

Un texto que empieza por $ permite meter variables entre llaves: es la interpolación de cadenas.

Ejemplo práctico
string nombre = "Ana";
int edad = 30;
decimal precio = 19.99m;   // la m indica decimal
var socio = true;          // bool

Console.WriteLine($"{nombre} tiene {edad} años");
Console.WriteLine($"Total: {precio * 3}");   // Total: 59,97 (con Windows en español)

Ejercicio

¿Qué tipo es el más adecuado para importes de dinero en C#?

Ver la solución

Respuesta: decimal. decimal trabaja en base 10 y evita errores de redondeo típicos de double con céntimos.

Lección 3 · Básico

Condiciones y switch

if, else if y else deciden qué se ejecuta. Los textos se comparan con ==, que en C# compara su contenido.

La expresión switch devuelve un valor según el caso, y _ recoge todos los que no coinciden con los anteriores.

Ejemplo práctico
int nota = 7;

string resultado = nota switch
{
    >= 9 => "Sobresaliente",
    >= 5 => "Aprobado",
    _ => "Suspenso",
};
Console.WriteLine(resultado); // Aprobado

Ejercicio

En una expresión switch, ¿qué significa _?

Ver la solución

Respuesta: Cualquier otro caso. _ es el patrón de descarte: coincide con todo lo que no encaje en los casos anteriores.

Encuentra los errores

Este programa debería mostrar «Fin de semana» para el día 6, pero no compila: tiene 2 errores.

Ver la solución
  • Línea 7: En C# cada sección de un switch tiene que terminar con break (o return); no se puede «caer» de un caso al siguiente. Corrección: tipo = "Fin de semana"; break;
  • Línea 12: Falta el punto y coma al final de la instrucción. Corrección: Console.WriteLine(tipo);

Lección 4 · Básico

Listas y bucles

List<T> es una lista que crece según añades elementos; el tipo va entre < y >. Count dice cuántos hay.

foreach recorre cada elemento; for usa un contador cuando necesitas la posición.

Ejemplo práctico
var compra = new List<string> { "pan", "leche" };
compra.Add("huevos");

foreach (var producto in compra)
{
    Console.WriteLine($"- {producto}");
}
Console.WriteLine(compra.Count); // 3

Ejercicio

Completa el bucle que recorre la lista.

 (var p in compra)
{
    Console.WriteLine(p);
}
Ver la solución

Respuesta: foreach. foreach (var x in lista) toma cada elemento por turno.

Escribe el código

Escribe el método static int ContarPares(List<int> numeros): devuelve cuántos números pares hay en la lista. Recórrela con foreach.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
static int ContarPares(List<int> numeros)
{
    int pares = 0;
    foreach (int n in numeros)
    {
        if (n % 2 == 0)
        {
            pares++;
        }
    }
    return pares;
}

El contador empieza en 0 y sube con pares++ cada vez que el resto de dividir entre 2 es 0. Con LINQ sería numeros.Count(n => n % 2 == 0).

Nivel 2 Intermedio

Lección 5 · Intermedio

Métodos

Un método agrupa código reutilizable. Se declara con el tipo que devuelve (void si nada), el nombre y los parámetros con su tipo. Los parámetros pueden tener valor por defecto.

En un programa con instrucciones de nivel superior puedes declarar funciones locales al final del archivo.

Ejemplo práctico
Console.WriteLine(ConIva(100m));        // 121,00
Console.WriteLine(ConIva(100m, 10m));   // 110,0

static decimal ConIva(decimal precio, decimal iva = 21m)
{
    return precio * (1 + iva / 100);
}

Ejercicio

¿Qué devuelve ConIva(200m) con el IVA por defecto del 21 %?

Ver la solución

Respuesta: 242,00. 200 × 1,21 = 242. Como es decimal, conserva los decimales del cálculo y, con el sistema en español, se muestra como 242,00.

Escribe el código

Escribe static double Media(double[] notas): suma las notas con un bucle y devuelve la media. Si el array está vacío, devuelve 0 para no dividir entre cero.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
static double Media(double[] notas)
{
    if (notas.Length == 0)
    {
        return 0;
    }
    double suma = 0;
    foreach (double nota in notas)
    {
        suma += nota;
    }
    return suma / notas.Length;
}

Primero se descarta el caso vacío; después se acumula la suma y se divide entre notas.Length. Como suma es double, la división conserva los decimales.

Conceptos clave

Lección 6 · Intermedio

Clases, propiedades y records

Una clase agrupa datos y comportamiento. Las propiedades ({ get; set; }) exponen los datos y permiten controlar cómo se leen y se cambian.

Un record es una forma corta de declarar tipos que solo guardan datos; compara por valor y su ToString() muestra el contenido.

Ejemplo práctico
var cuenta = new Cuenta("Ana");
cuenta.Ingresar(50);
Console.WriteLine(cuenta.Saldo);   // 50

var p = new Producto("Ratón", 12.5m);
Console.WriteLine(p);              // Producto { Nombre = Ratón, Precio = 12,5 }

class Cuenta(string titular)
{
    public string Titular { get; } = titular;
    public decimal Saldo { get; private set; }
    public void Ingresar(decimal cantidad) => Saldo += cantidad;
}

record Producto(string Nombre, decimal Precio);

Ejercicio

Con public decimal Saldo { get; private set; }, ¿quién puede cambiar el saldo?

Ver la solución

Respuesta: Solo el código de la propia clase. El set es privado: fuera de la clase solo se puede leer.

Lección 7 · Intermedio

Consultas con LINQ

LINQ añade a las colecciones métodos para filtrar (Where), transformar (Select), ordenar (OrderBy) o sumar (Sum), usando lambdas como p => p.Precio > 10.

Funciona igual con listas, con XML o con bases de datos a través de Entity Framework.

Ejemplo práctico
var productos = new List<Producto>
{
    new("Teclado", 25.90m), new("Ratón", 12.50m), new("Cable", 4.99m),
};

var caros = productos
    .Where(p => p.Precio > 10)
    .OrderBy(p => p.Nombre)
    .Select(p => p.Nombre);

Console.WriteLine(string.Join(", ", caros));          // Ratón, Teclado
Console.WriteLine(productos.Sum(p => p.Precio));      // 43,39

record Producto(string Nombre, decimal Precio);

Ejercicio

Completa el método LINQ que filtra.

var baratos = productos.(p => p.Precio < 5);
Ver la solución

Respuesta: Where. Where devuelve los elementos que cumplen la condición.

Encuentra los errores

Este programa debería mostrar «2 caros, total 65» (los precios de más de 20 € y lo que suman). Tiene 2 errores.

Ver la solución
  • Línea 2: El enunciado pide los de más de 20 €; con p > 40 no entra ninguno (el de 40 tampoco, porque no es mayor que 40). Corrección: var caros = precios.Where(p => p > 20);
  • Línea 3: Suma todos los precios en lugar de solo los caros. Corrección: decimal total = caros.Sum();

Nivel 3 Avanzado

Lección 8 · Avanzado

Interfaces y herencia

Una interfaz declara qué métodos debe tener una clase; una clase puede implementar varias. La herencia (: ClaseBase) reutiliza el código de otra clase, y virtual y override permiten cambiar un método heredado.

Por convención, los nombres de interfaz empiezan por I: IAvisador, IRepositorio.

Ejemplo práctico
IFigura[] figuras = { new Circulo(1), new Rectangulo(2, 3) };
foreach (var f in figuras) Console.WriteLine(f.Area().ToString("F2"));

interface IFigura
{
    double Area();
}
record Circulo(double Radio) : IFigura
{
    public double Area() => Math.PI * Radio * Radio;
}
record Rectangulo(double Ancho, double Alto) : IFigura
{
    public double Area() => Ancho * Alto;
}

Ejercicio

¿De cuántas interfaces puede implementar una clase en C#?

Ver la solución

Respuesta: De varias. Una clase hereda de una sola clase base, pero puede implementar todas las interfaces que quiera.

Lección 9 · Avanzado

Excepciones y null seguro

try, catch y finally tratan los errores. Para convertir textos sin excepciones, métodos como int.TryParse devuelven true o false.

Con los tipos de referencia que aceptan null (activados por defecto en proyectos nuevos), string? indica que puede faltar, y el compilador avisa si lo usas sin comprobarlo. ?. y ?? ayudan a manejarlo.

Ejemplo práctico
if (int.TryParse("42", out int numero))
    Console.WriteLine(numero + 1);   // 43

string? apodo = null;
Console.WriteLine(apodo?.ToUpper() ?? "Sin apodo");

try
{
    var texto = File.ReadAllText("datos.txt");
}
catch (FileNotFoundException)
{
    Console.WriteLine("No existe el archivo");
}

Ejercicio

¿Qué indica el ? en string? apodo?

Ver la solución

Respuesta: Que puede valer null. string? es un texto que puede ser null; el compilador te pedirá comprobarlo antes de usarlo.

Lección 10 · Avanzado

Programación asíncrona con async/await

Las operaciones lentas, como descargar algo, devuelven una Task. Con await esperas su resultado sin bloquear el programa, y el método que lo usa se marca con async.

Así una aplicación de escritorio no se congela mientras descarga y un servidor puede atender otras peticiones a la vez.

Ejemplo práctico
using var http = new HttpClient();

try
{
    string html = await http.GetStringAsync("https://example.com");
    Console.WriteLine($"Descargados {html.Length} caracteres");
}
catch (HttpRequestException e)
{
    Console.WriteLine($"Error de red: {e.Message}");
}

Ejercicio

Completa para esperar el resultado de la tarea.

string texto =  http.GetStringAsync(url);
Ver la solución

Respuesta: await. await espera a que termine la Task y devuelve su resultado sin bloquear el hilo.

Lección 11 · Avanzado

Dictionary y HashSet

Dictionary<TClave, TValor> guarda pares clave-valor y encuentra un valor por su clave al instante, sin recorrer nada. Usa TryGetValue para leer una clave que quizá no exista: con el indexador (dic[clave]) obtendrías una excepción.

HashSet<T> es una colección sin elementos repetidos: Add devuelve false si el elemento ya estaba. Es ideal para comprobar si algo ya se ha visto o para quitar duplicados de una lista.

Ejemplo práctico
var stock = new Dictionary<string, int>
{
    ["manzanas"] = 12,
    ["peras"] = 0,
};
stock["kiwis"] = 7;                     // añade o sustituye

if (stock.TryGetValue("plátanos", out int cantidad))
    Console.WriteLine($"Hay {cantidad}");
else
    Console.WriteLine("No tenemos plátanos");

var vistos = new HashSet<string>();
foreach (var correo in new[] { "ana@ejemplo.es", "luis@ejemplo.es", "ana@ejemplo.es" })
{
    if (!vistos.Add(correo)) Console.WriteLine($"Repetido: {correo}");
}
Console.WriteLine(vistos.Count);        // 2

Escribe el código

Escribe static Dictionary<string, int> ContarPalabras(string texto): separa el texto por espacios y devuelve cuántas veces aparece cada palabra.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
static Dictionary<string, int> ContarPalabras(string texto)
{
    var cuenta = new Dictionary<string, int>();
    foreach (string palabra in texto.Split(' ', StringSplitOptions.RemoveEmptyEntries))
    {
        cuenta[palabra] = cuenta.GetValueOrDefault(palabra) + 1;
    }
    return cuenta;
}

GetValueOrDefault(palabra) devuelve 0 si la palabra todavía no está, así que la misma línea sirve para la primera vez y para las siguientes.

Lección 12 · Avanzado

Coincidencia de patrones

La coincidencia de patrones comprueba la forma de un valor y extrae sus datos a la vez. is pregunta por un tipo o una condición (if (forma is Circulo c)), y las expresiones switch eligen un resultado según el primer patrón que encaja.

Los patrones pueden combinar tipos, propiedades ({ Edad: >= 18 }), rangos (< 0) y _, que encaja con cualquier cosa. El compilador avisa si un caso nunca podrá darse.

Ejemplo práctico
Console.WriteLine(Tarifa(new Cliente("Ana", 70)));    // Mayor: 5 €
Console.WriteLine(Tarifa(new Cliente("Luis", 10)));   // Infantil: 3 €
Console.WriteLine(Tarifa(new Cliente("Marta", 35)));  // General: 8 €

static string Tarifa(Cliente c) => c switch
{
    { Edad: < 12 } => "Infantil: 3 €",
    { Edad: >= 65 } => "Mayor: 5 €",
    _ => "General: 8 €",
};

record Cliente(string Nombre, int Edad);

Ejercicio

En una expresión switch, ¿qué hace el patrón _?

Ver la solución

Respuesta: Encaja con cualquier valor que no haya encajado antes. _ es el descarte: encaja siempre, por eso se pone al final como caso por defecto.

Lección 13 · Avanzado

Archivos y JSON con System.Text.Json

File.WriteAllText y File.ReadAllText guardan y leen un archivo de texto en una sola línea. Para guardar objetos, conviértelos a JSON con JsonSerializer.Serialize y recupéralos con JsonSerializer.Deserialize<T>.

Leer un archivo puede fallar (no existe, no hay permiso o el JSON está mal), así que conviene comprobar File.Exists o capturar las excepciones IOException y JsonException.

Ejemplo práctico
using System.Text.Json;

var tareas = new List<Tarea> { new("Comprar pan", false), new("Llamar al médico", true) };

string json = JsonSerializer.Serialize(tareas);
File.WriteAllText("tareas.json", json);
Console.WriteLine(json);
// [{"Texto":"Comprar pan","Hecha":false},{"Texto":"Llamar al m\u00E9dico","Hecha":true}]

var leidas = JsonSerializer.Deserialize<List<Tarea>>(File.ReadAllText("tareas.json"));
Console.WriteLine(leidas![1].Texto);   // Llamar al médico

record Tarea(string Texto, bool Hecha);

Ejercicio

Completa para convertir el texto JSON de nuevo en una lista de tareas.

var lista = JsonSerializer.<List<Tarea>>(json);
Ver la solución

Respuesta: Deserialize. Serialize convierte un objeto en JSON y Deserialize<T> hace lo contrario, creando objetos del tipo T.

Lección 14 · Avanzado

Fechas: DateTime, DateOnly y TimeSpan

DateOnly representa solo una fecha (un cumpleaños), TimeOnly solo una hora y DateTime ambas cosas. Restar dos DateTime da un TimeSpan, una duración con Days, Hours o TotalMinutes.

ToString("dd/MM/yyyy") muestra una fecha con el formato que quieras y DateOnly.ParseExact lee un texto con un formato concreto. Las fechas no se modifican: AddDays(30) devuelve una nueva.

Ejemplo práctico
var inicio = new DateOnly(2026, 1, 15);
var entrega = inicio.AddDays(30);
Console.WriteLine(entrega.ToString("dd/MM/yyyy"));       // 14/02/2026
Console.WriteLine(entrega.DayOfWeek);                    // Saturday

var llegada = new DateTime(2026, 5, 4, 9, 0, 0);
var salida = new DateTime(2026, 5, 4, 17, 30, 0);
TimeSpan jornada = salida - llegada;
Console.WriteLine(jornada.TotalHours);                   // 8,5 (con el sistema en español)

var fecha = DateOnly.ParseExact("25/12/2026", "dd/MM/yyyy");
Console.WriteLine(fecha > entrega);                      // True

Ejercicio

¿Qué tipo obtienes al restar dos DateTime?

Ver la solución

Respuesta: Un TimeSpan (una duración). La diferencia entre dos instantes es una duración, TimeSpan, de la que puedes sacar días, horas o minutos.

Lección 15 · Avanzado

Pruebas automáticas con xUnit

xUnit es una de las bibliotecas de pruebas más usadas en .NET. Cada prueba es un método con el atributo [Fact] que comprueba un resultado con Assert.Equal(esperado, obtenido); con [Theory] e [InlineData] la misma prueba se repite con varios datos.

Se crea un proyecto de pruebas con dotnet new xunit, se añade una referencia al proyecto que quieres probar y se ejecutan todas con dotnet test.

Ejemplo práctico
// Proyecto de pruebas creado con: dotnet new xunit
using Xunit;

public class CalculadoraTests
{
    [Fact]
    public void SumaDosNumeros()
    {
        Assert.Equal(5, Calculadora.Sumar(2, 3));
    }

    [Theory]
    [InlineData(4, true)]
    [InlineData(7, false)]
    [InlineData(0, true)]
    public void DetectaPares(int numero, bool esperado)
    {
        Assert.Equal(esperado, Calculadora.EsPar(numero));
    }
}

public static class Calculadora
{
    public static int Sumar(int a, int b) => a + b;
    public static bool EsPar(int n) => n % 2 == 0;
}

Ejercicio

¿Para qué sirve [Theory] con varios [InlineData]?

Ver la solución

Respuesta: Para ejecutar la misma prueba con distintos datos. Cada [InlineData] pasa un juego de argumentos al método: así compruebas varios casos sin repetir código.

¿Has terminado? Sigue practicando

Repasa los conceptos en el diccionario, practica con estas herramientas o prueba otro lenguaje: las ideas que has aprendido (variables, condiciones, funciones, objetos…) te servirán en cualquiera.

Proyectos para practicar C#

  • Texto y programación

    Base64, URL y JSON

    Codifica y decodifica Base64 y URL, y valida o formatea JSON con la línea del error.

  • Texto y programación

    Comparar dos textos

    Encuentra las diferencias entre dos versiones de un texto, línea a línea y palabra a palabra.

Fuentes consultadas

* Porcentaje de personas que dijeron haber usado C# en la Stack Overflow Developer Survey 2026: Technology (pregunta sobre lenguajes de programación, de scripts y de marcado; todas las respuestas). El progreso de las lecciones se guarda solo en este navegador.