cv.cm

Programación · Lenguajes de programación

Tutorial de C# para principiantes

7 min de lectura

Respuesta corta

C# es un lenguaje orientado a objetos de tipado estático creado por Microsoft que se ejecuta en .NET, un entorno de ejecución gratuito y multiplataforma para Windows, macOS y Linux. Instala el SDK de .NET, ejecuta dotnet new console y dotnet run imprime «Hello, World!». Las ideas centrales son las clases y los records, las colecciones genéricas como List<T> y Dictionary<K, V>, las consultas LINQ, async/await, el pattern matching y las excepciones.

Abrir Formateador JSON

Qué es C# y para qué se usa

C# (se pronuncia «C sharp») fue diseñado en Microsoft por un equipo dirigido por Anders Hejlsberg y se publicó por primera vez en 2002. Está estandarizado por Ecma (ECMA-334) y se desarrolla de forma abierta junto con .NET. C# se usa para APIs y sitios web (ASP.NET Core), aplicaciones de escritorio para Windows, aplicaciones móviles y de escritorio multiplataforma (.NET MAUI), servicios en la nube y videojuegos: el motor Unity usa C# para los scripts de juego. Su sintaxis pertenece a la familia de C, así que resulta familiar a quienes conocen Java, C++ y JavaScript.

Instalar .NET y ejecutar Hello, world

Descarga el SDK de .NET desde dot.net y compruébalo con dotnet --version. Crea y ejecuta un proyecto de consola:

dotnet new console -o HelloApp
cd HelloApp
dotnet run
# Hello, World!

La plantilla crea Program.cs, que usa instrucciones de nivel superior, así que un programa pequeño no necesita una clase ni un método Main:

Console.WriteLine("Hello, World!");

Variables y tipos básicos

Cada variable tiene un tipo estático. var permite que el compilador lo infiera a partir del valor. Los tipos de valor (números, bool, struct) contienen sus datos directamente; los tipos de referencia (string, clases, arrays) se refieren a un objeto.

TipoEjemploNotas
intint n = 42;Entero de 32 bits
longlong big = 10_000_000_000;Entero de 64 bits
doubledouble x = 3.14;Punto flotante de 64 bits
decimaldecimal price = 19.99m;Base 10, bueno para dinero
boolbool ok = true;true o false
charchar c = 'A';Unidad de código UTF-16
stringstring s = "hi";Texto inmutable
var name = "Ada";
int count = 7;
double avg = count / 2.0;
Console.WriteLine($"{name} has {count} items, avg {avg}");  // Ada has 7 items, avg 3.5
Console.WriteLine(count / 2);                              // 3 (integer division)
Console.WriteLine(int.Parse("12") + 1);                    // 13
string? nickname = null;   // ? marks a nullable reference
Console.WriteLine(nickname ?? "none");                     // none

$"..." es una cadena interpolada. Los proyectos nuevos activan los tipos de referencia que aceptan null, así que el compilador avisa cuando se usa sin comprobar un valor que puede ser null.

Control de flujo y métodos

int score = 72;
if (score >= 90) Console.WriteLine("A");
else if (score >= 70) Console.WriteLine("B");   // B
else Console.WriteLine("C");

for (int i = 0; i < 3; i++) Console.Write($"{Square(i)} ");  // 0 1 4
Console.WriteLine();

foreach (var fruit in new[] { "apple", "pear" }) Console.WriteLine(fruit);

string day = "SAT";
string kind = day switch
{
    "SAT" or "SUN" => "weekend",
    _ => "weekday",
};
Console.WriteLine(kind);   // weekend
Console.WriteLine(Greet("Lin"));                // Hello, Lin!

static int Square(int x) => x * x;
static string Greet(string name, string greeting = "Hello") => $"{greeting}, {name}!";

La expresión switch y el patrón or forman parte del pattern matching de C#. Los métodos pueden tener valores por defecto en los parámetros, argumentos con nombre (Greet(name: "Lin")) y cuerpos de expresión con =>.

Clases, propiedades y records

Las clases tienen campos, propiedades (campos con lógica de get y set) y métodos. Un record es un tipo cuya igualdad se basa en sus valores, ideal para datos inmutables.

var acc = new Account("Ann");
acc.Deposit(50);
Console.WriteLine($"{acc.Owner}: {acc.Balance}");   // Ann: 50

var p1 = new Point(3, 4);
var p2 = p1 with { Y = 5 };                         // copy with one change
Console.WriteLine(p1);                              // Point { X = 3, Y = 4 }
Console.WriteLine(p1 == new Point(3, 4));           // True

public class Account
{
    public string Owner { get; }
    public decimal Balance { get; private set; }

    public Account(string owner) => Owner = owner;

    public void Deposit(decimal amount)
    {
        if (amount <= 0) throw new ArgumentException("amount must be positive");
        Balance += amount;
    }
}

public record Point(int X, int Y);

Las declaraciones de tipos deben ir después de las instrucciones de nivel superior en el mismo archivo. Las clases admiten herencia simple (class Savings : Account) y cualquier número de interfaces.

Colecciones y LINQ

Las colecciones genéricas de System.Collections.Generic son las estructuras de datos de uso diario. LINQ (Language Integrated Query) añade a toda colección métodos como Where, Select, OrderBy y GroupBy.

var nums = new List<int> { 3, 1, 2, 5 };
nums.Add(4);
nums.Sort();
Console.WriteLine(string.Join(", ", nums));      // 1, 2, 3, 4, 5

var ages = new Dictionary<string, int> { ["ann"] = 31, ["bob"] = 25 };
ages["cy"] = 40;
if (ages.TryGetValue("bob", out int bobAge)) Console.WriteLine(bobAge);  // 25

var evenSquares = nums.Where(n => n % 2 == 0).Select(n => n * n).ToList();
Console.WriteLine(string.Join(" ", evenSquares)); // 4 16

var oldest = ages.OrderByDescending(kv => kv.Value).First();
Console.WriteLine(oldest.Key);                    // cy

Otras colecciones habituales son HashSet<T> para elementos únicos, Queue<T>, Stack<T> y los arrays de tamaño fijo como int[].

Característica distintiva: async y await

C# introdujo el modelo async/await que muchos lenguajes adoptaron después. Un método async devuelve un Task y puede hacer await de operaciones lentas, como peticiones web o lecturas de archivos, sin bloquear un hilo.

using System.Net.Http;

using var http = new HttpClient();
var sizes = await Task.WhenAll(
    GetLengthAsync(http, "https://example.com"),
    GetLengthAsync(http, "https://example.org"));
Console.WriteLine($"downloaded {sizes.Sum()} characters");

static async Task<int> GetLengthAsync(HttpClient http, string url)
{
    string html = await http.GetStringAsync(url);
    return html.Length;
}

Task.WhenAll ejecuta ambas peticiones a la vez y espera a las dos. La declaración using var libera el HttpClient al final del ámbito.

Excepciones

Los errores son excepciones. Captura primero los tipos específicos y usa finally o using para la limpieza. El patrón TryParse evita las excepciones con entradas que a menudo no son válidas.

try
{
    int age = int.Parse("abc");
}
catch (FormatException ex)
{
    Console.WriteLine($"not a number: {ex.Message}");
}
finally
{
    Console.WriteLine("done");
}

if (!int.TryParse("41", out int parsed)) parsed = 0;
Console.WriteLine(parsed);   // 41

Un programa completo pequeño: frecuencia de palabras

Sustituye Program.cs en un proyecto de consola por este código y ejecuta dotnet run -- notes.txt.

using System.Text.RegularExpressions;

if (args.Length != 1)
{
    Console.Error.WriteLine("usage: dotnet run -- FILE");
    return 1;
}

try
{
    string text = File.ReadAllText(args[0]).ToLowerInvariant();
    var top = Regex.Matches(text, "[a-z']+")
        .GroupBy(m => m.Value)
        .Select(g => (Word: g.Key, Count: g.Count()))
        .OrderByDescending(t => t.Count)
        .Take(5);

    foreach (var (word, count) in top)
        Console.WriteLine($"{count,5}  {word}");
    return 0;
}
catch (FileNotFoundException)
{
    Console.Error.WriteLine($"file not found: {args[0]}");
    return 1;
}

Las instrucciones de nivel superior pueden leer los args de la línea de comandos y devolver un código de salida. System.IO, System.Linq y System.Collections.Generic se importan automáticamente mediante los implicit usings de los proyectos nuevos.

Errores comunes de principiantes

  • Usar double para dinero; usa decimal para evitar errores de redondeo binario.
  • Ignorar las advertencias de nulabilidad, que luego se convierten en NullReferenceException en tiempo de ejecución.
  • Modificar una List<T> dentro de un foreach sobre esa misma lista, lo que lanza InvalidOperationException.
  • Crear un HttpClient nuevo para cada petición en una aplicación de larga duración. Reutiliza una sola instancia o usa IHttpClientFactory.
  • Olvidar que las consultas LINQ se ejecutan de forma diferida: la consulta se vuelve a ejecutar cada vez que se enumera, salvo que llames a ToList().

Adónde ir después

Microsoft Learn tiene la documentación oficial de C#, con un recorrido por el lenguaje y tutoriales interactivos. Después aprende ASP.NET Core para APIs web, Entity Framework Core para bases de datos o Unity para videojuegos, y usa xUnit o NUnit para las pruebas. Visual Studio, VS Code con el C# Dev Kit y JetBrains Rider son los editores habituales.

Preguntas frecuentes

¿C# es solo para Windows?

No. El .NET moderno se ejecuta en Windows, macOS y Linux, y las aplicaciones C# pueden desplegarse en servidores Linux y contenedores. Solo algunos frameworks de interfaz de escritorio, como WPF y Windows Forms, son exclusivos de Windows.

¿Cuál es la diferencia entre C# y .NET?

C# es el lenguaje de programación. .NET es el entorno de ejecución y las bibliotecas que compilan y ejecutan el código C# (y también F# y Visual Basic).

¿C# se parece a Java?

Sí, en muchos aspectos: ambos son lenguajes orientados a objetos, de tipado estático y con recolector de basura, con sintaxis de estilo C. C# añade características como las propiedades, LINQ, async/await, los structs de tipo valor y los records.

¿C# es bueno para desarrollar videojuegos?

Sí. Unity, uno de los motores de juego más usados, emplea C# para los scripts, y el motor Godot también admite C#.

Todos los tutoriales de programación