Back to Blog
C#

C# Public Keyword: Scope and Access Control

c# public keyword: Understand the C# public keyword, its role in access control, interaction with other modifiers, and practical implications for API design.

access modifiersC# scopeencapsulationmember visibilitypublic members
A stylized C# code editor showing a public access modifier applied to a method, with a visible key or lock metaphor.

The c# public keyword defines the most permissive access level in C#. A member or type marked public is accessible from any other code in the same assembly or a different assembly that references it. This seems simple, but using public in the right places is central to designing clean, maintainable APIs.

What the public Keyword Actually Allows

When you apply public to a class, method, property, field, or other type member, you remove all access restrictions. Consider a simple public method:

public class Calculator { public int Add(int a, int b) { return a + b; } }

Any code that can reference the Calculator class can call Add. This includes code outside the assembly, such as a separate application or library. Without public, the method would only be visible within the same assembly depending on the default accessibility, which is private for class members and internal for top-level types.

Accessibility Levels Compared

C# provides several access modifiers. The public keyword sits at the most permissive end. The other modifiers are:

  • private – accessible only within the containing type.
  • protected – accessible within the containing type and derived types.
  • internal – accessible within the same assembly.
  • protected internal – accessible within the same assembly or by derived types.
  • private protected – accessible within the containing type or derived types, but only in the same assembly.

Use public intentionally. It is not a default; you must explicitly declare a member as public to expose it beyond the assembly. For example, a class declared as public can be used by another project only if its members are also public. A public class with no public members exposes no functionality to external callers.

Interaction with Other Modifiers

The public keyword can combine with static, readonly, const, virtual, abstract, override, and async, among others. The access modifier is independent of these behaviors. For example, a public static method is both globally accessible and callable without an instance:

public static class MathHelper { public static int Square(int value) { return value * value; } }

A public const field is a compile-time constant that is directly embedded in referencing code. Changing its value requires recompiling all consumers. A public static readonly field is also accessible from anywhere but its value is evaluated at runtime, providing more flexibility.

public class Settings { public static readonly string DefaultConnectionString; public const int MaxRetryCount = 3; }

Here, MaxRetryCount is a public const, so any code referencing it copies the value 3 at compile time. DefaultConnectionString is a public static readonly field that is initialized in a static constructor or at declaration, and its value is fetched at runtime.

Public Members and API Design

Once a member is public, it becomes part of your API surface. Changing it later can break consumers. Adding a new public method is usually a non-breaking change, but removing one, changing its signature, or altering its behavior is breaking. This is why exposing public is a commitment.

A common mistake is marking internal helpers as public without considering the consequences. For example, a public method that is only used by tests encourages other teams to use it, creating unintended dependencies.

public class OrderProcessor { public decimal CalculateTax(Order order) { // Internal calculation } }

If CalculateTax was meant for internal business logic, making it public allows any consumer to call it, potentially with invalid orders. Better to keep it private or internal unless you intend it for external use.

Default Accessibility and Common Pitfalls

The default accessibility for class members is private. For top-level types, the default is internal. Many developers forget this and wonder why a class they wrote is not visible to a referencing project. The fix is to explicitly mark the type and its desired members as public.

Another pitfall is marking a class as public but leaving its members without access modifiers. The result is a type that external code can instantiate but cannot interact with, because all members are private.

public class TemperatureSensor { double _celsius; double GetFahrenheit() { ... } }

Even though TemperatureSensor is public, _celsius and GetFahrenheit are private. External code can create an instance but cannot read the temperature. If the intention is to expose a temperature value, the property or method must be public.

Deciding Where to Use Public

There is no automatic rule that all code inside a public class must be public. Keep fields and implementation details private. Make methods public only when the caller is outside the class. If the caller is in the same assembly but not necessarily the same class, internal is a better fit.

The choice also depends on library vs application code. In a library, every public member is part of your contract. In an application, the boundaries matter less because there is usually only one assembly, but using public still affects testability and future refactoring. A public class in an application is exposed to integration tests and other internal projects, so its surface still needs scrutiny.

Public and Inheritance

Public members are inherited by derived classes. If a base class has a public method, all derived types will also have that method, unless it is overridden or hidden. This is part of the contract for derived classes. For example:

public class Animal { public void Eat() { ... } } public class Dog : Animal { }

A Dog instance can call Eat because it is inherited from Animal. Overriding a public method with public override preserves its public visibility. Changing the access level in an override is not allowed; you cannot make an overridden method private or internal.

Public vs Internal for Cross-Assembly Usage

When designing a solution with multiple assemblies, public is the only way to expose types to other assemblies. internal types are invisible outside the assembly, but you can expose internal members to specific friend assemblies using InternalsVisibleTo. This is useful for unit tests, which often need access to internals. However, do not fall back on InternalsVisibleTo when a type should simply be public.

For most shared code, prefer internal over public unless external consumption is a requirement. This keeps your API surface small and reduces accidental coupling. For example, a helper class used by several classes within the same library should be internal, not public, even if the library is intended for external use.

Maintainability Implications of Over-Exposing

Each public member adds maintenance burden. Documentation generators, XML documentation warnings, and versioning policies apply to public members. Tools like API analyzers flag missing XML comments on public members, and source-compatibility checks fail when public APIs change. Minimizing unnecessary public surface keeps the codebase easier to evolve.

Consider refactoring a public method that was only used internally. Change its access to private or internal to see if any external callers break. In a well-structured solution, the compiler will reveal unintended dependencies, allowing you to tighten the API. This is a practical way to apply the principle of least privilege without abstract theorizing.

c# public keyword: Practical Usage and Code Examples | RYUSLOG DEV