Volver al inicio

Elvis modificador de acceso en C# — control de acceso estático

Elvis modificador de acceso — un conjunto de atributos y analizadores Roslyn para C# que de forma declarativa permiten acceso a miembros privados e internos para tipos estrictamente especificados. La solución se verifica en tiempo de compilación, no requiere reflexión en tiempo de ejecución e integra en CI/CD.

Elvis en C#: friend seguro sin romper la encapsulación
Advertisement 728x90

Modificador de acceso Elvis en C#: Cómo implementar un acceso controlado a miembros privados sin romper la encapsulación

El modificador de acceso Elvis es un sistema de atributos personalizados y un analizador Roslyn para C# que permite, de manera declarativa, un acceso controlado a los miembros private e internal de una clase mediante tipos 'amigables' estrictamente limitados. A diferencia del friend de C++, Elvis no requiere modificar la clase objetivo al agregar nuevos consumidores de confianza y ofrece una validación estricta en tiempo de compilación. Esta solución está dirigida a desarrolladores de nivel intermedio o senior que trabajan en proyectos de alto rendimiento, multihilo o dependientes de frameworks, donde los principios de inmutabilidad por defecto y mutabilidad localizada son fundamentales para garantizar la correcta ejecución.

Cómo funciona Elvis: arquitectura y componentes clave

Elvis consta de tres capas interconectadas:

  • Atributos en tiempo de compilación[Elvis], [OnlyType], [OnlyNs], [ExcludeType], [ExcludeNs]. Estos anotan miembros (propiedades, métodos, campos), especificando qué tipos o espacios de nombres tienen permiso de acceso.
  • Analizador Roslyn — realiza comprobaciones semánticas en el IDE y durante la compilación: analiza las llamadas, determina el contexto de acceso ('atractor'), lo compara con las reglas de los atributos y emite mensajes de diagnóstico cuando se producen violaciones.
  • Extensión VSIX y paquete NuGet — asegura la integración en el flujo de trabajo de desarrollo: autocompletado de atributos, aplicación rápida de plantillas y visualización de las relaciones permitidas en el árbol de decisiones.

Es importante destacar que Elvis no modifica el código IL, no utiliza reflexión en tiempo de ejecución ni depende de InternalsVisibleTo. Toda la validación se realiza de forma estática—durante la compilación. Esto garantiza que el acceso prohibido no se escape incluso en versiones de depuración.

Google AdInline article slot

Casos de uso: desde escenarios básicos hasta avanzados

Consideremos una tarea básica: la clase Me contiene private decimal Money, pero necesita permitir que otra clase, MyFriend, modifique su valor de forma segura sin otorgar acceso a todo el código del ensamblado.

class Me
{
    [Elvis(typeof(MyFriend))]
    private decimal _money = 100;
    public decimal Money => _money;

    public void SetMoney(decimal value) => _money = value;
}

class MyFriend
{
    public void TakeHalf(Me me)
    {
        var half = me.Money / 2;
        me.SetMoney(me.Money - half); // ✅ Permitido: MyFriend está especificado en [Elvis]
        // me._money = 0; // ❌ Error en tiempo de compilación: el acceso directo a un campo privado está prohibido
    }
}

Las nuevas características añadidas en las versiones 2026-03 y 2026-04 incluyen:

  • Tipos mediante nombres de cadena: [Elvis("MyNamespace.MyFriend")]—útil para conexiones entre ensamblados cuando no es posible referenciar un tipo directamente.
  • Expresiones regulares: [Elvis(@"^MyNamespace\.Tests\.*$")]—permite acceder a todas las clases de prueba dentro de un espacio de nombres.
  • Atributos de exclusión: [ExcludeType(typeof(ObsoleteHelper))]—bloquea un tipo específico incluso si cumple con la regla general [OnlyNs].
  • Espacios de nombres: [OnlyNs("MyCompany.Core")]—restringe el acceso únicamente a los tipos del espacio de nombres especificado.

Combinatoria y comportamiento en jerarquías

Elvis admite reglas compuestas. Cuando se aplican múltiples atributos a un mismo miembro, por defecto se utiliza lógica AND, pero las excepciones tienen prioridad:

Google AdInline article slot
  • Si se especifican tanto [OnlyNs("A")] como [ExcludeNs("A.Sub")], se concede acceso a A. excepto A.Sub..
  • En la herencia: si la clase base está marcada con [Elvis(typeof(BaseFriend))] y la clase derivada con [Elvis(typeof(DerivedFriend))], ambos tipos obtienen acceso a los miembros de la clase base. Sin embargo, la clase derivada no hereda los derechos sobre sus propios miembros private—estos deben ser anotados por separado.
  • Para métodos sobrecargados, los atributos se aplican individualmente a cada firma.

Cabe destacar que Elvis no afecta a los miembros protected—estos siguen siendo accesibles según las reglas estándar de herencia. Los atributos solo funcionan con miembros private e internal.

Qué importa

  • Elvis no es un reemplazo para DI ni para interfaces; es una capa adicional de control de encapsulación en situaciones donde las interfaces resultan poco prácticas (por ejemplo, al trabajar con DTOs, objetos de configuración o mecanismos internos de frameworks).
  • Todas las reglas se verifican de forma estática—sin sobrecarga en tiempo de ejecución, totalmente compatible con la compilación AOT y con la reducción de código.
  • El soporte para expresiones regulares y tipos basados en cadenas hace que la solución sea aplicable en escenarios donde los tipos no están disponibles en tiempo de compilación (arquitecturas de plugins, generación de código).
  • Los atributos de exclusión ([Exclude*]) permiten crear “listas blancas con excepciones”, lo cual es crucial al migrar grandes bases de código.
  • Elvis no viola SOLID: no debilita DIP sino que lo complementa, permitiendo formalizar contratos entre componentes sin crear interfaces de envoltorio artificiales.

Limitaciones prácticas y recomendaciones

Elvis no resuelve todos los problemas de acceso. Debe utilizarse cuando:

  • Se necesita un acceso preciso y predecible al estado private sin exponerlo a través de APIs public/internal.
  • La arquitectura requiere una estricta separación de zonas de responsabilidad (por ejemplo, el núcleo del framework y sus extensiones).
  • Se emplean patrones donde las clases están lógicamente estrechamente acopladas pero deben mantenerse independientes desde el punto de vista de la compilación (por ejemplo, Builder y BuilderResult).

Evite usar Elvis cuando:

Google AdInline article slot
  • Sea necesario refactorizar dependencias mal diseñadas—si necesita 15 clases friend para un solo campo private, es probable que la lógica pertenezca a un servicio separado.
  • No existan pipelines CI/CD, por lo que los analizadores no se incluyen en el proceso de construcción—las reglas serán ignoradas fuera del IDE.
  • Para miembros public—los atributos simplemente los ignoran, ya que el acceso ya está abierto.

Al implementar en un proyecto existente, comience con campos private en clases DTO y fábricas internas, donde el riesgo de violar invariantes es mayor y el costo de refactorización es menor.

— Editorial Team

Advertisement 728x90

Leer después