Zpět na domů

Elvis-modifikátor přístupu v C# — statická kontrola přístupu

Elvis-modifikátor přístupu — sada atributů a Roslyn-analyzaátorů pro C#, umožňující deklarativně povolovat přístup k private a internal členům striktně určeným typům. Řešení je ověřováno v fázi kompilace, nevyžaduje runtime reflexi a integruje se do CI/CD.

Elvis v C#: bezpečný friend bez porušení inkapsulace
Advertisement 728x90

Modifikátor přístupu Elvis v C#: jak implementovat kontrolovaný přístup k privátním členům bez porušení inkapsulace

Elvis-modifikátor přístupu je uživatelský systém atributů a Roslyn-analyzátorů pro C#, který umožňuje deklarativně povolit přístup k private a internal členům tříd pouze striktně omezeným „přátelským“ typům. Na rozdíl od C++ friend nevyžaduje Elvis změnu cílové třídy při přidávání nových důvěryhodných konzumentů a zajišťuje rigorózní kontrolu na fázi kompilace. Řešení je určeno pro middle/senior vývojáře pracující s vysoce zatíženými, multithreadovanými nebo frameworkem závislými projekty, kde jsou principy neměnnosti (immutable-by-default) a lokalizované měnitelnosti kritické pro správnost.

Jak funguje Elvis: architektura a klíčové komponenty

Elvis se skládá ze tří vzájemně propojených vrstev:

  • Atributy časové kompilace[Elvis], [OnlyType], [OnlyNs], [ExcludeType], [ExcludeNs]. Tyto atributy anotují členy (vlastnosti, metody, pole), čímž určují, které typy nebo jmenné prostory mají právo na přístup.
  • Roslyn-analyzátor — provádí semantickou kontrolu v IDE i při buildu: analyzuje volání, určuje kontext přístupu („atraktor“), srovnává jej s pravidly atributů a vydává diagnostická hlášení při porušení.
  • VSIX rozšíření a NuGet balíček — zajišťují integraci do pracovního procesu: automatické doplňování atributů, rychlé aplikování šablon, vizualizace povolených vazeb v rozhodovacím stromu.

Důležité: Elvis nezmění IL kód, nepoužívá reflexi v runtime ani se nespoléhá na InternalsVisibleTo. Všechna kontrola probíhá staticky — při kompilaci. To zaručuje, že zakázaný přístup neprojde ani v debug buildu.

Google AdInline article slot

Příklady použití: od základního scénáře po pokročilé situace

Vezměme si základní úlohu: třída Me obsahuje private decimal Money, ale má umožnit jiné třídě MyFriend bezpečně upravovat jeho hodnotu, aniž by se otevíral přístup celému kódu v sestavení.

class Me
{
    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); // ✅ Povoleno: MyFriend je uveden v [Elvis]
        // me._money = 0; // ❌ Chyba kompilace: přímý přístup k privátnímu poli je zakázán
    }
}

Nové možnosti přidané ve verzích 2026-03 a 2026-04:

  • Typy prostřednictvím řetězců: [Elvis("MyNamespace.MyFriend")] — užitečné při cross-build spojení, kdy není možné odkázat na typ.
  • Pravidelné výrazy: [Elvis(@"^MyNamespace\.Tests\.*$")] — povoluje přístup všem testovacím třídám v daném jmenném prostoru.
  • Vylučující atributy: [ExcludeType(typeof(ObsoleteHelper))] — blokuje konkrétní typ, i když spadá pod obecné pravidlo [OnlyNs].
  • Jménné prostory: [OnlyNs("MyCompany.Core")] — omezuje přístup pouze typům z uvedeného jmenného prostoru.

Kombinatorika a chování v hierarchii

Elvis podporuje složená pravidla. Pokud je na jednom členovi několik atributů, standardně se používá logika AND, ale s prioritou výjimek:

Google AdInline article slot
  • Pokud jsou uvedeny [OnlyNs("A")] a [ExcludeNs("A.Sub")], pak je přístup povolen pro A., kromě A.Sub..
  • Při dědictví: pokud je základní třída označena [Elvis(typeof(BaseFriend))], a odvozená [Elvis(typeof(DerivedFriend))], pak oba typy dostanou přístup k členům základní třídy. Ovšem odvozená třída ne zdědí práva na své vlastní private členy — ty musí být anotovány zvlášť.
  • U přetížených metod se atributy aplikují na každou signaturu individuálně.

Zvláštnost: Elvis nemá žádný vliv na protected členy — ti zůstávají dostupní podle standardních pravidel dědictví. Atributy fungují pouze s private a internal.

Co je důležité

  • Elvis není náhradou DI nebo rozhraní, ale další úroveň kontroly nad inkapsulací tam, kde rozhraní nejsou vhodná (například při práci s DTO, konfiguračními objekty nebo interními frameworkovými mechanismy).
  • Všechna pravidla jsou kontrolována staticky — žádný overhead v runtime, plná kompatibilita s AOT kompilací a trimmingem.
  • Podpora pravidelných výrazů a řetězcových jmen typů činí řešení použitelným i v scénářích, kde typy nejsou dostupné v době kompilace (pluginové architektury, generace kódu).
  • Vylučující atributy ([Exclude*]) umožňují vytvářet „bílé seznamy s výjimkami“, což je kritické při migraci velkých kódových základů.
  • Elvis nenarušuje SOLID: neoslabuje DIP, ale ho doplňuje, umožňuje formalizovat smlouvy mezi komponentami bez vytváření umělých interfeisů‑obalů.

Praktická omezení a doporučení

Elvis neřeší všechny problémy s přístupem. Jeho použití je vhodné, když:

  • Je potřeba bodový, předvídatelný přístup k private stavu bez jeho odhalení prostřednictvím public/internal API.
  • Architektura předpokládá striktní izolaci zón odpovědnosti (například jádro frameworku a jeho rozšíření).
  • Používají se vzorce, kde jsou třídy logicky těsně propojeny, ale mají zůstat nezávislé z hlediska kompilace (například Builder a BuilderResult).

Nepoužívejte Elvis:

Google AdInline article slot
  • Místo refaktoringu špatně navržených závislostí — pokud potřebujete 15 friend tříd pro jedno private pole, nejspíš by logika měla být vytažena do samostatného servisu.
  • V projektech bez CI/CD, kde analyzátoři nejsou součástí pipeline — pak budou pravidla ignorována při buildu mimo IDE.
  • Pro public členy — atributy jsou prostě ignorovány, protože přístup je tak jako tak otevřen.

Při implementaci do existujícího projektu se doporučuje začít s private poli v DTO třídách a interních fabrikách, kde je riziko porušení invariantů nejvyšší a náklady na refaktoring minimální.

— Editorial Team

Advertisement 728x90

Číst dál