Menu

Métodos en C#: parámetros, valores de retorno, params, sobrecarga y Main

Cómo declarar y llamar a métodos en C#: tipos de retorno y void, parámetros, métodos con cuerpo de expresión, devolver varios valores con una tupla, sobrecarga, arrays params, métodos static frente a métodos de instancia, el método Main, recursividad y funciones locales.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Un método es un bloque de código con nombre que recibe entradas (parámetros), hace su trabajo y puede devolver un resultado (el valor de retorno). En C#, todo método vive dentro de un tipo; no hay funciones sueltas.

Salida:

Headphones   48.00
Cable        11.40

Anatomía de un método

public static decimal PriceWithTax(decimal net, decimal taxRate)
{
    return net * (1 + taxRate);
}
  • Modificador de acceso (public, private, ...): quién puede llamarlo. Si se omite, un método es private de su tipo.
  • static: el método pertenece al tipo, no a un objeto. Más sobre esto abajo.
  • Tipo de retorno: el tipo del valor que devuelve, o void si no devuelve nada.
  • Nombre: en PascalCase por convención, normalmente un verbo (Calculate, SendEmail, TryParse).
  • Lista de parámetros: cero o más pares type name. Cada parámetro es una variable local inicializada con el argumento de quien llama.
  • Cuerpo: las sentencias entre las llaves.

El nombre más los tipos de los parámetros (y cualquier modificador ref, out o in) forman la firma del método. El tipo de retorno no forma parte de ella, lo que importa para la sobrecarga.

Valores de retorno y void

return termina el método de inmediato y devuelve un valor. Un método con tipo de retorno debe devolver un valor de ese tipo en todos los caminos que lo recorren:

static string Grade(int score)
{
    if (score >= 50)
        return "pass";
}   // error CS0161: 'Program.Grade(int)': not all code paths return a value

El compilador comprueba cada rama. Añade el return "fail"; que falta, o un throw si llegar a ese punto es un bug.

Un método void no devuelve nada. Aun así puede usar un return; sin valor para salir antes:

Salida:

ana@mail.com: newsletter sent
ben@mail.com: not subscribed, skipped
False
True

Métodos con cuerpo de expresión

Cuando el cuerpo de un método es una sola expresión, => sustituye a las llaves y al return:

static decimal Discount(decimal price) => price * 0.1m;
static bool IsAdult(int age) => age >= 18;
static void Log(string message) => Console.WriteLine($"[log] {message}");

Es el mismo método, escrito más corto. También funciona con métodos void, siempre que el cuerpo sea una expresión (una llamada, una asignación).

Los parámetros son copias

Por defecto, los argumentos se pasan por valor: el parámetro recibe una copia del argumento. Asignar al parámetro dentro del método no afecta a la variable de quien llama:

Salida:

inside: 42
after:  21
scores: 70, 85, 100

La List cambió porque, en un tipo de referencia, el valor copiado es la referencia: el método y quien llama apuntan al mismo objeto lista. Reasignar scores = new List<int>() dentro del método no afectaría a quien llama. Para que un método pueda asignar a la variable de quien llama, usa ref o out; para dar valores por defecto a los parámetros y llamarlos por nombre, usa parámetros opcionales y argumentos con nombre.

Devolver varios valores con una tupla

Desde C# 7.0, un método puede devolver una tupla con elementos con nombre, y quien llama puede deconstruirla en variables separadas:

Salida:

min 15, max 27, avg 21.0
range 12

_ descarta el elemento que no necesitas. Para resultados que se usan en muchos sitios, una clase pequeña o un record con propiedades con nombre documenta la forma mejor que una tupla.

Sobrecarga

Varios métodos pueden compartir nombre si sus listas de parámetros se diferencian en el número, los tipos o el orden de los tipos. El compilador elige la mejor coincidencia para cada llamada:

Salida:

12.57
13.50
25

Area(5) elige la versión int porque es una coincidencia exacta; Area(2.0) elige la double. Los métodos no pueden diferenciarse solo por el tipo de retorno: dos métodos Area(int) que devuelven int y double son el error CS0111, Type 'Program' already defines a member called 'Area' with the same parameter types, porque una llamada como Area(5) no podría distinguirlos.

Una llamada que ninguna sobrecarga acepta también falla en tiempo de compilación: Area(1, 2, 3) es CS1501, No overload for method 'Area' takes 3 arguments, y Area("big") es CS1503, Argument 1: cannot convert from 'string' to 'double'.

params: un número variable de argumentos

Un parámetro array params acepta cualquier número de argumentos, incluido ninguno. El compilador construye el array por ti:

Salida:

empty cart: 0 item(s), 0
one item: 1 item(s), 9.99
three items: 3 item(s), 19.75
from array: 2 item(s), 50

Reglas: params debe ser el último parámetro (si no, CS0231), un método tiene como mucho uno, y pasar un array existente entrega ese array tal cual. Console.WriteLine("{0} and {1}", a, b) y string.Format son métodos params object[]. C# 13 extendió params a otros tipos de colección como List<T> y ReadOnlySpan<T>; antes solo se permitían arrays.

Métodos static y de instancia

Un método static pertenece al tipo y se llama sobre el nombre del tipo: Math.Max(3, 7), Program.Total(...). Un método de instancia pertenece a un objeto y puede usar los campos de ese objeto:

Salida:

12

Llamar a un método de instancia desde uno static sin un objeto es el error de principiante más común en C#:

class Program
{
    void Greet() => Console.WriteLine("hi");

    static void Main()
    {
        Greet();
        // error CS0120: An object reference is required for the non-static field, method, or property 'Program.Greet()'
    }
}

Marca Greet como static, o crea un objeto y llámalo sobre él: new Program().Greet();. La página de static explica con más detalle los miembros static y las clases static.

El método Main

Main es donde empieza un programa. Debe ser static, y estas firmas son válidas:

static void Main() { }
static void Main(string[] args) { }            // command-line arguments
static int Main(string[] args) { return 0; }   // exit code for the shell
static async Task Main() { }                    // C# 7.1 and later

La forma async Task Main permite que el punto de entrada use await directamente (consulta async y await). Desde C# 9, un archivo puede contener en su lugar instrucciones de nivel superior: código sin clase ni Main, como en un proyecto nuevo de dotnet new console. El compilador genera la clase y el método Main a su alrededor, así que es lo mismo escrito más corto; args sigue disponible.

Recursividad

Un método puede llamarse a sí mismo. Cada llamada tiene sus propios parámetros y variables locales, y un caso base debe detener la cadena:

Salida:

120
2432902008176640000
35

Sin un caso base, o con una entrada demasiado profunda, cada llamada añade un marco a la pila hasta que el programa muere con StackOverflowException. Esa excepción no se puede capturar, así que para entradas de profundidad desconocida, reescribe la recursividad como un bucle.

Funciones locales

C# 7.0 introdujo las funciones locales: métodos declarados dentro de otro método y visibles solo ahí. Pueden usar las variables del método que las contiene:

// C# 7.0 and later
static int CountValid(string[] codes)
{
    int count = 0;
    foreach (string code in codes)
    {
        if (IsValid(code)) count++;
    }
    return count;

    bool IsValid(string c) => c.Length == 6 && char.IsLetter(c[0]);
}

Mantienen un método auxiliar junto a su único llamador sin exponerlo al resto de la clase. Desde C# 8 pueden marcarse como static para prohibir que capturen variables externas. Donde no se dispone de una función local, un método privado static en la misma clase hace el mismo trabajo, como en los demás ejemplos de esta página.

Preguntas frecuentes

¿Qué diferencia hay entre un método y una función en C#?

En C#, toda función se declara dentro de una clase, un struct o un record, y se llama método. No hay funciones sueltas. Incluso las instrucciones de nivel superior (C# 9) y las funciones locales (C# 7) se compilan como métodos de un tipo generado o del tipo que las contiene. "Función" se usa de manera informal, y para lambdas y delegados.

¿Qué hace params en C#?

params permite que un método reciba un número variable de argumentos de un tipo: static int Sum(params int[] numbers) puede llamarse como Sum(), Sum(4), Sum(4, 5, 6) o Sum(existingArray). El compilador empaqueta los argumentos en un array. El parámetro params debe ser el último, y un método solo puede tener uno.

¿Qué es el método Main en C#?

Main es el punto de entrada del programa: el método al que el runtime llama primero. Debe ser static, puede devolver void o int (el código de salida) y puede recibir string[] args con los argumentos de la línea de comandos. Desde C# 7.1 también puede ser async Task Main, y desde C# 9 las instrucciones de nivel superior permiten que el compilador lo genere por ti.

¿Cómo devuelvo varios valores desde un método de C#?

Devuelve una tupla: static (int Min, int Max) Range(int[] values) y return (min, max);. Quien llama puede leer result.Min o deconstruirla con var (lo, hi) = Range(values);. Para valores que viajan juntos por tu código, una clase pequeña o un record es más claro; los parámetros out son la alternativa más antigua.

¿Por qué obtengo "An object reference is required for the non-static field, method, or property"?

Un método static como Main llamó directamente a un método de instancia. Los métodos static pertenecen al tipo y no tienen un objeto sobre el que llamarlo. Marca como static el método llamado, o crea antes un objeto: var app = new Program(); app.Greet();.

¿Qué significa "not all code paths return a value"?

Error CS0161: un método con tipo de retorno tiene al menos un camino que llega a la llave de cierre sin un return. Normalmente un if devuelve un valor pero no hay un else ni un return final después. Añade un return (o un throw) al final del método.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR