# C# Language Design Meeting for July 22nd, 2026

## Agenda

- [Allowing `safe` on non-`extern` members](#allowing-safe-on-non-extern-members)

## Quote of the Day

- Nothing particularly noteworthy was said today, sorry

## Discussion

### Allowing `safe` on non-`extern` members

Champion issue: https://github.com/dotnet/csharplang/issues/9704  
Spec: https://github.com/dotnet/csharplang/blob/e48234bad43d13f37ef2ffd836e90474cb800054/proposals/unsafe-evolution.md#allow-safe-on-non-extern-members-libraryimport

We revisited where the `safe` modifier may be used. We had
[previously decided](./LDM-2026-05-13.md#safe-marker-syntax) that `extern` members must be marked either `safe` or
`unsafe`, and that `safe` should only be permitted where the language requires that explicit choice. The libraries team
encountered a problem with that restriction while applying the model to `LibraryImport`.

`LibraryImport` generates the P/Invoke implementation for a user-authored partial method. For a blittable signature,
the generator can implement that method directly as an `extern`. For a signature that requires marshalling, it instead
generates a managed wrapper whose body performs the marshalling and calls a separate `extern` local function. The
user-facing declaration is not itself `extern` in either case.

This makes the user's ability to write `safe` depend on the implementation selected by the generator. In the direct
case, the generated `extern` implementation needs a safety modifier, and the partial declarations need to agree. In the
wrapper case, the user-facing implementation is not `extern`, so the current rules do not permit `safe` on it. The
choice between those implementations is not part of the `LibraryImport` contract and can change as the generator
evolves, but the user needs one consistent place to attest that the generated P/Invoke is safe to call.

The proposal was therefore to permit `safe` as a declaration modifier anywhere that `unsafe` can mark a declaration as
caller-unsafe. On a declaration that is already safe by default, the modifier has no effect. The language would not
special-case `LibraryImport`; instead, its analyzer would require the user-authored partial method to be marked either
`safe` or `unsafe`.

We considered retaining the current language rules and adding a property such as `Safe = true` to `LibraryImport`.
That would give the source generator a separate attestation mechanism, but it would also give generated P/Invokes a
different user experience from manually declared P/Invokes. It would not by itself resolve the requirement that the
user-authored and generated parts of a directly generated partial `extern` agree on their safety modifier.

The main concern with allowing `safe` more broadly was that it could become an attractive nuisance. Most C# methods are
already safe by default, so `safe` would ordinarily be redundant. Readers might infer that the modifier enables extra
checking, or projects might adopt a convention of placing it on every ordinary method. We discussed several ways to
limit that risk: allowing it only on partial members, allowing it when an attribute requests an explicit safety choice,
or having the IDE fade unnecessary uses.

Each restriction added another rule to a feature intended for a narrow part of the language. We also observed that
developers generally encounter `safe` only after entering the unsafe-code model, so ordinary C# documentation and
examples are unlikely to encourage widespread use. In unsafe-heavy code, explicitly marking caller-safe members could
even be a reasonable local convention. We did not think an IDE fade was necessary, particularly since the IDE does not
generally fade redundant declaration modifiers such as an explicit default accessibility.

We clarified that this proposal does not change override or interface implementation rules. In particular, it does not
allow an override to change a caller-safe base member into a caller-unsafe member.

Finally, we clarified that our existing distinction between caller safety and body context continues to apply. A `safe`
modifier on a local function says that calling the function does not require an unsafe context; it does not establish a
safe context for the function body. If the local function is declared within an unsafe context, that context continues
to apply inside its body. This proposal does not revisit that distinction or introduce a `safe` block or expression for
returning to a safe body context.

#### Conclusion

We will allow `safe` as a declaration modifier everywhere that `unsafe` can mark a declaration as caller-unsafe, with
no additional restrictions or coordinated IDE fading. Where the declaration is safe by default, `safe` is a no-op.
Nested local function bodies do not become safe contexts as a result of the modifier.
