static marca un miembro que pertenece al tipo, no a un objeto. Un campo de instancia existe una vez por objeto; un campo static existe una vez y punto. Un método de instancia se ejecuta sobre un objeto (order.Ship()); un método static se ejecuta sobre el tipo (Math.Round(x)) y no necesita ningún objeto.
Campos static: una copia, compartida
La forma más clara de ver la diferencia es un contador. Cada Order tiene su propio Id, pero el número de pedidos creados hasta el momento tiene que vivir en algún sitio compartido.
Salida:
Ana #1, Ben #2, Chen #3
Orders created: 3
A los miembros static se accede a través del nombre del tipo: Order.Created, no a.Created. C# no permite llegar a un miembro static a través de una instancia (error CS0176), lo que deja claro en el punto de llamada que el valor es compartido.
Métodos static
Un método static no tiene this. Solo trabaja con sus parámetros y con otros miembros static. Eso lo convierte en la forma natural de los métodos auxiliares que calculan algo a partir de sus entradas:
Salida:
61.38 EUR
7
El propio Main es static: el runtime lo llama antes de que exista ningún objeto.
El error CS0120: código static que usa miembros de instancia
Como Main es static, los principiantes suelen encontrarse este error en cuanto añaden un segundo método:
class Program
{
int visits;
void Greet() { Console.WriteLine("Hello"); }
static void Main()
{
Greet(); // error CS0120: An object reference is required for the non-static field, method, or property 'Program.Greet()'
visits++; // error CS0120: An object reference is required for the non-static field, method, or property 'Program.visits'
}
}
Un método static no tiene objeto, así que no hay ningún visits que incrementar. Hay dos soluciones, y la correcta depende de lo que sea el miembro:
- El miembro es de verdad un dato de cada objeto: crea un objeto y úsalo.
var app = new Program(); app.Greet(); - El miembro no necesita estado de objeto: márcalo como
static.static void Greet() { ... }
Marcar todo como static para que desaparezca el error funciona en un programa diminuto y se convierte en estado global en uno real. Decide preguntándote si el dato pertenece a un objeto o a todo el programa.
La dirección contraria siempre está permitida: los métodos de instancia pueden llamar libremente a los miembros static.
Clases static
Una clase declarada static solo puede contener miembros static y nunca puede instanciarse. Es un grupo con nombre de funciones y constantes.
Salida:
77
100
True
El compilador hace cumplir el contrato: un miembro de instancia dentro de una clase static es el error CS0708, y new sobre ella es el error CS0712. Math, Console, File, Path y Enumerable son todas clases static. Los métodos de extensión también deben vivir en una clase static.
Constructores static y static readonly
Un campo static cuyo valor necesita más de una expresión puede asignarse en un constructor static, que el runtime ejecuta una vez, antes de que el tipo se use por primera vez:
Salida:
Before first use
ShippingRates initialized
5.50
3
2026-01-01
readonly significa que el campo solo puede asignarse en su inicializador o en el constructor static. Después no puede reasignarse, aunque el objeto al que apunta aún podría ser mutable, y por eso el diccionario se expone como IReadOnlyDictionary.
const frente a static readonly
Los dos te dan un valor con nombre compartido por todo el programa, y se diferencian en cuándo se fija el valor:
const | static readonly | |
|---|---|---|
| El valor se fija en | tiempo de compilación | tiempo de ejecución, una vez |
| Tipos permitidos | números, char, bool, string, enums, null | cualquier tipo |
| Implícitamente static | sí (no se puede escribir static const) | se declara static de forma explícita |
| Se copia en los ensamblados que lo usan | sí | no, se lee del campo |
Se puede usar en cases de switch y argumentos de atributos | sí | no |
La fila de la copia importa en las bibliotecas: si el ensamblado A declara public const int MaxItems = 50; y el ensamblado B lo usa, B contiene el literal 50. Cambiar A a 100 no hace nada en B hasta que B se recompila. Para valores que pueden cambiar entre versiones, prefiere static readonly.
using static
Una directiva using static (C# 6) importa los miembros static de un tipo, para que puedas llamarlos sin el nombre del tipo:
Salida:
Area: 28.27
40
Se lee bien en código con muchas matemáticas. En otros casos, úsala con moderación: un WriteLine o un Parse sueltos ocultan de qué tipo viene el método.
Estado static e hilos
Un campo static lo comparten todos los hilos del proceso. Incrementarlo desde varios hilos a la vez pierde actualizaciones, porque created++ es una lectura, una suma y una escritura, y dos hilos pueden intercalarlas. Usa Interlocked.Increment(ref created) para los contadores, o un lock para cualquier cosa más grande. Las aplicaciones web son el sitio habitual donde esto muerde: cada petición se ejecuta en algún hilo, así que un campo static que guarde "el usuario actual" lo comparten todos los usuarios.
Un detalle más: en una clase genérica, cada tipo construido tiene sus propios campos static. Cache<int>.Count y Cache<string>.Count son dos campos distintos.
Cuándo usar static
Buenos usos:
- Métodos auxiliares puros que calculan un resultado a partir de sus argumentos: formato, conversiones, validación.
- Constantes y datos de consulta de solo lectura compartidos por el programa.
- Métodos de fábrica, como
TimeSpan.FromMinutes(5)o tu propioTemperature.FromCelsius(21). - Métodos de extensión.
Malos usos:
- Estado global mutable (el usuario actual, un carrito de la compra, una configuración que cambia). Acopla todo el código que lo toca y hace que las pruebas dependan de lo que dejaron las demás.
- Todo lo que vayas a querer sustituir en las pruebas, como un reloj, una base de datos o un servicio de envío de correo. Las llamadas static no pueden sustituirse por una falsa; una instancia pasada al constructor sí.
Preguntas frecuentes
¿Qué significa static en C#?
Un miembro static pertenece al propio tipo y no a ningún objeto. Hay una sola copia de un campo static, existan los objetos que existan, y un método static se llama sobre el nombre del tipo (Math.Max(3, 7)) sin crear ninguna instancia. Un método static no tiene this, así que no puede usar directamente los campos de instancia.
¿Qué es una clase static en C#?
Una clase declarada static solo puede contener miembros static y nunca puede instanciarse (new sobre ella es el error CS0712). Agrupa funciones relacionadas que no necesitan estado de objeto, como Math, Console o File. Los métodos de extensión deben declararse en una clase static.
¿Cómo arreglo "An object reference is required for the non-static field, method, or property"?
Es el error CS0120: un método static, a menudo Main, está usando un miembro de instancia. O bien crea un objeto y llama al miembro sobre él (var app = new Program(); app.Run();), o bien marca el miembro como static si no necesita datos de cada objeto.
¿Qué diferencia hay entre const y static readonly en C#?
Un const es una constante de tiempo de compilación (números, strings, bool, enums) cuyo valor se copia en todos los ensamblados que la usan. Un campo static readonly se asigna una vez en tiempo de ejecución, en su inicializador o en el constructor static, y puede ser de cualquier tipo, como un DateTime o una lista. Usa const para los valores que nunca cambiarán y static readonly para todo lo demás.
¿Cuándo debo usar static en C#?
Usa static para el código que solo trabaja con sus parámetros (métodos auxiliares puros, como un formateador de precios), para constantes y cachés que de verdad son globales al programa, y para los métodos de fábrica. Evita static para todo lo que guarde estado por usuario o por petición, o que vayas a querer sustituir en las pruebas: el estado mutable compartido es difícil de probar y no es seguro entre hilos sin bloqueos.