Menu

Modificadores de acceso en C#: public, private, protected e internal

Los seis modificadores de acceso de C# (public, private, protected, internal, protected internal, private protected), qué permite cada uno, los valores por defecto cuando no escribes ninguno y los errores de compilación que producen.

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

Los modificadores de acceso deciden qué código puede usar un tipo o un miembro. Son la forma en que una clase separa la parte de la que depende el resto del código (su superficie pública) de la parte que puede cambiar libremente (sus detalles internos). C# tiene seis, más los valores por defecto que obtienes cuando no escribes ninguno.

Los seis modificadores

ModificadorTipo que lo contieneTipo derivado, mismo ensambladoOtro código, mismo ensambladoTipo derivado, otro ensambladoOtro código, otro ensamblado
publicsísísísísí
protected internalsísísísíno
protectedsísínosíno
internalsísísínono
private protectedsísínonono
privatesínononono

Un ensamblado es un proyecto compilado: el .dll o .exe que produce. "Mismo ensamblado" significa "mismo proyecto", y por eso internal funciona como "visible para mi código, oculto para los usuarios de mi biblioteca".

private y public: encapsulamiento

private es el valor por defecto de los miembros y la opción correcta para la mayoría de los campos. La clase expone lo que necesita quien la usa mediante métodos y propiedades public, y esos métodos mantienen coherentes los datos del objeto.

Salida:

False
True
Priya: 180

Como balance es privado, las únicas formas de cambiarlo son Deposit y TryWithdraw, y las dos imponen las reglas. Ningún otro código del programa puede crear un saldo negativo, se escriba como se escriba. Eso es lo que da el encapsulamiento: las reglas viven en un solo sitio.

private es por tipo, no por objeto. Un método de Account puede leer other.balance en otra instancia de Account, y así es como suelen escribirse Equals y los métodos de comparación.

protected: visible para las clases derivadas

Un miembro protected está oculto para el resto del mundo pero disponible para las clases que heredan de la clase que lo declara.

Salida:

Sam: 4000.00
Rita: 6400.00

Hay una regla que sorprende a la gente. Dentro de Manager, puedes usar baseSalary sobre this o sobre otro Manager, pero no sobre un Employee cualquiera:

class Manager : Employee
{
    public decimal Compare(Employee other)
    {
        return baseSalary - other.baseSalary;
        // error CS1540: Cannot access protected member 'Employee.baseSalary' via a qualifier
        // of type 'Employee'; the qualifier must be of type 'Manager' (or derived from it)
    }
}

El motivo: other podría ser un Contractor que también deriva de Employee, y protected da acceso a tu propia rama de la jerarquía, no a la de tus hermanos.

Un campo protected ata para siempre a todas las clases derivadas a ese campo. Muchas bases de código prefieren una propiedad o un método protected, o mantienen los campos privados y dan a las clases derivadas solo lo que necesitan.

internal: visible dentro del ensamblado

Los miembros y tipos internal puede usarlos cualquier código del mismo proyecto y nada de fuera. Una biblioteca lo usa para los métodos auxiliares de los que dependen sus clases públicas:

// In the MyShop.Pricing library project
public class PriceCalculator
{
    public decimal Total(Cart cart) => TaxRules.Apply(cart.Subtotal);
}

internal static class TaxRules   // callers of the library cannot see this class
{
    internal static decimal Apply(decimal amount) => amount * 1.2m;
}

Los tipos de nivel superior son internal cuando no escribes ningún modificador. Por eso una clase que olvidaste marcar como public en una biblioteca de clases "falta" en el proyecto que la referencia.

Los proyectos de pruebas unitarias son ensamblados aparte, así que tampoco pueden ver el código internal. La solución habitual es un atributo en la biblioteca:

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyShop.Pricing.Tests")]

protected internal y private protected

Estos dos combinan los modificadores anteriores, de formas opuestas, y sus nombres se confunden con facilidad:

  • protected internal es protected o internal: cualquier código del mismo ensamblado, más las clases derivadas de otros ensamblados. Es el más amplio.
  • private protected (C# 7.2) es protected e internal: solo las clases derivadas que además están en el mismo ensamblado. Es el más restringido, para una clase base cuyos puntos de extensión deben quedarse dentro de tu biblioteca.
public class ReportBase
{
    protected internal string Title;      // same assembly, or subclasses anywhere
    private protected int RowLimit = 500; // subclasses in this assembly only (C# 7.2)
}

Valores por defecto cuando no escribes nada

DóndePor defectoPermitidos
Tipo declarado en un namespaceinternalpublic, internal, file (C# 11)
Miembro de una claseprivatelos seis
Miembro de un structprivatepublic, internal, private (los structs no se pueden heredar)
Tipo anidadoprivatelos seis
Miembro de una interfazpublicmodificadores explícitos permitidos desde C# 8
Miembro de un enumpublicninguno

Ser explícito cuesta una palabra y le ahorra una duda al siguiente lector, así que la mayoría de las guías de estilo piden el modificador aunque coincida con el valor por defecto.

Acceso a nivel de accesor

Una propiedad o un indexador pueden dar a uno de sus accesores un modificador más restringido que el de la propia propiedad:

Salida:

report.pdf: 100%

El modificador del accesor debe ser más restrictivo que el de la propiedad, y solo uno de los dos accesores puede tenerlo.

Accesibilidad incoherente

Un miembro público no puede exponer un tipo menos visible que él mismo, porque quien lo llama recibiría algo que no tiene permiso para nombrar:

internal class Discount { }

public class Checkout
{
    public Discount Current() => null;
    // error CS0050: Inconsistent accessibility: return type 'Discount' is less accessible than method 'Checkout.Current()'
}

La misma regla da CS0051 con los tipos de los parámetros y CS0053 con los tipos de las propiedades. Arréglalo haciendo el tipo tan visible como el miembro, o el miembro tan oculto como el tipo.

Elegir un modificador

Empieza por el modificador más restrictivo que funcione y amplíalo solo cuando alguien que llama lo necesite. Hacer algo más visible después es fácil y nunca rompe nada; hacerlo menos visible rompe a todos los que lo usaban. En la práctica: los campos private, los miembros que forman el propósito de la clase public, los métodos auxiliares private y los tipos exclusivos de la biblioteca internal.

Preguntas frecuentes

¿Cuáles son los modificadores de acceso de C#?

Hay seis: public (cualquier código), private (solo el tipo que lo contiene), protected (el tipo que lo contiene y los tipos derivados de él), internal (cualquier código del mismo ensamblado), protected internal (el mismo ensamblado, o tipos derivados en cualquier sitio) y private protected (tipos derivados del mismo ensamblado, C# 7.2). C# 11 añadió file para tipos visibles solo en un archivo fuente.

¿Cuál es el modificador de acceso por defecto en C#?

Los miembros de una clase o de un struct (campos, métodos, propiedades, tipos anidados) son private por defecto. Los tipos de nivel superior (una clase declarada directamente en un namespace) son internal por defecto. Los miembros de las interfaces y de los enums son públicos.

¿Qué significa protected en C#?

Un miembro protected es visible dentro de su propia clase y dentro de cualquier clase derivada de ella, pero no para el resto del código. Una clase derivada solo puede usarlo a través de su propio tipo: dentro de Manager, this.baseSalary funciona, pero llegar a baseSalary sobre otro objeto Employee cualquiera es el error CS1540.

¿Qué es internal en C#?

internal hace que un tipo o miembro sea visible para todo el código del mismo ensamblado (el .dll o .exe compilado del mismo proyecto) e invisible fuera de él. Así es como una biblioteca mantiene sus clases auxiliares fuera de su API pública. [assembly: InternalsVisibleTo("MyLib.Tests")] permite que un proyecto de pruebas también las vea.

¿Cómo arreglo "is inaccessible due to its protection level"?

Es el error CS0122: el código usa un miembro que no tiene permiso para ver, muy a menudo un campo que se quedó con el private por defecto. Si el código externo debe leerlo, expón una propiedad o un método públicos; no hagas público el campo sin más. Si es un tipo de otro proyecto, probablemente sea internal.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR