Proposal: add explicit named API views #10285
Full Proposal DocumentThe complete Exhibits proposal is available here: Proposal: Add explicit named API views to C# Goal: Allow library authors to organize the public members of a class or namespace into named, workflow-oriented API views, improving discoverability without changing runtime behavior. Justification: Mature framework types often expose dozens or hundreds of public members serving very different purposes. As frameworks grow, every intended usage scenario contributes additional public members. A developer configuring a service, diagnosing failures, or performing administration must navigate through the entire API to discover the members pertinent to the task. As this naturally divides the API into workflow-specific categories, this proposal simply provides a formal means for declaring this organization in the language. The proposal applies to APIs a principle analogous to relational database views: different workflows present different projections of the same underlying API. This permits developers to work with domain-focused subsets of an API while leaving all runtime behavior unchanged. To assert safety, the following design invariant applies: any valid program using API views compiles to the same computation as if the API views were absent. Motivation: Why a language feature? API organization already exists in practice. Today, API organization in C# is represented only indirectly through documentation, naming conventions, and tooling heuristics. These are useful, but all outside the language itself. The proposal introduces named API views as a first-class language declaration. Are there other options? Existing tooling can infer API organization, but it is necessarily heuristic and tool-specific. The proposal allows API authors to declare that organization once, providing a single authoritative source for IDEs, analyzers, documentation generators, and source generators. Employing attributes has the drawback of introducing metadata that becomes part of the compiled assembly. Interfaces likewise introduce semantic contracts, which the proposed API views intentionally avoid. API views are designed to have zero impact beyond semantic analysis. Inheritance also provides a limited form of organization, but only by imposing a strict hierarchical structure. Inheritance organizes behavior through specialization rather than projection, making it unsuitable for grouping members by workflow. Non-goals:
I'm particularly interested in feedback on two questions:
|
Replies: 4 comments 57 replies
|
This feels wrong. It's a way to organize apis into sensible chunks to be used. But we already have that today. That's what classes/interfaces/etc. are for. They exist so that you can properly create sensible apis. If you're not using those, then it's not for the language to try to paper over that for you. |
Very well, first I propose to introduce a term for a named API surface that a class or namespace supports. Considering the analogy of a museum curator selecting artifacts to put on display, I choose the name exhibit. Unlike museum pieces, however, the inclusion of exhibits does not restrict its employment, and an item may freely appear in any number of exhibits without restriction. In the formal proposal I give further rules to keep the employment of exhibits from propagating and complicating the codebase. A few clarifications are in order: Exhibit names are introduced in the class signature and may also occupy the modifier position of a method of the class to signal its inclusion. In method declarations, exhibit names appear before the return type, in the same syntactic position as public. They behave like contextual modifiers, proxy for public accessibility, and the keyword public may be included or omitted without effect. Anywhere public is permitted as a modifier, a declared exhibit name may be substituted. To restate the principle of non-interference: Exhibit markers do not participate in overload resolution, metadata or runtime behavior. In the example, exhibit names appear as modifiers and the return type (void, bool, etc.) remains in its normal position. The example provides a feel of how exhibits may be declared inline with the class: By way of a brief justification: The class signature includes the base class and any interfaces that may be implemented, and now also new exhibit names. To distinguish these from interfaces, I propose to prefix each new exhibit name with a contextual employment of the keyword out. It is contextual and syntactically disjoint from parameter and generic positions. This is consistent with the current employment of the keyword: in parameter signatures it means that "this flows outward from the method". In generics it means "this type parameter is only produced and never consumed". This aligns with exhibits, which represent outward-facing surfaces. It is produced by the type but not consumed by callers except to reveal names for employment scenarios. |
|
Exhibits introduce semantic partitions into the type system, enabling explicit API surfaces that attributes by design cannot express. Custom attributes provide a flexible mechanism for attaching metadata to program elements, and they are well-suited for scenarios where descriptive information is sufficient. However, attributes do not introduce structural organization into the type system. Exhibits define named API surfaces that the compiler can recognize and tooling can reliably consume. This distinction is not one of capability but of purpose: attributes annotate members, whereas Exhibits partition the API into conceptual views that are part of the language’s semantic model. From this foundation, the differences follow naturally:
None of this is a criticism of attributes; they serve their intended purpose well. Exhibits simply address a different design goal: providing explicit, compiler-recognized API surfaces that support structural organization, semantic partitioning, and in the future, layers of static reasoning. |
|
Attributes give metadata; Exhibits define structure. Treating those as interchangeable is a category mismatch — they operate at different semantic layers. One annotates, the other partitions. To answer the “How, exactly?” question: EIni → methods 1, 3, 5 Now, in another namespace, using the initialization surface is explicit: MyClass.EIni.Method1 And if you attempt something outside that surface — say MyClass.EIni.Method2 — the compiler reports: MyClass.Method2 is not a member of MyClass.EIni. That’s the semantic partition: |
Very well, first I propose to introduce a term for a named API surface that a class or namespace supports. Considering the analogy of a museum curator selecting artifacts to put on display, I choose the name exhibit. Unlike museum pieces, however, the inclusion of exhibits does not restrict its employment, and an item may freely appear in any number of exhibits without restriction. In the formal proposal I give further rules to keep the employment of exhibits from propagating and complicating the codebase.
A few clarifications are in order: Exhibit names are introduced in the class signature and may also occupy the modifier…