Back to Blog
C#

C# Interface Declaration: Syntax and Usage

Learn the syntax and rules for declaring C# interfaces, including members, access modifiers, and implementation patterns.

C# interfacesinterface syntaxinterface implementationtype contractsabstraction
An abstract illustration of a C# interface declaration showing a contract between types, with a blueprint-like design.

The C# interface declaration defines a contract that classes and structs can implement. It specifies the members that must be present without dictating how they are implemented. This article covers the syntax, member rules, and practical patterns for declaring interfaces in C#.

The Basic Syntax of an Interface Declaration

An interface is declared with the interface keyword followed by a name. By convention, interface names start with I. In the classic form, the body contains member signatures only; C# 8 and later also allow default implementations, covered later in this article.

public interface IShape { double Area { get; } void Draw(); }

This declares a contract that any implementing type must provide a read-only Area property and a Draw method. In this classic form, the interface contains no logic; it only describes what the implementing type must expose.

Interface Members and Their Implicit Rules

Interface members can be methods, properties, events, and indexers. A member without a body is implicitly public and abstract. A member with a default implementation is not abstract and can have an explicit access modifier (see Default Interface Members and Compatibility). Interfaces cannot contain instance fields, instance constructors, or finalizers. C# 8 and later also permit static members in interfaces, but this article focuses on instance members.

public interface IRepository { Task<T> GetByIdAsync<T>(int id); event EventHandler Changed; T this[int index] { get; set; } }

Property and indexer accessors are also part of the contract. An implementing class must provide the specified getter and setter behavior, though it can choose to implement them as expression-bodied members or with a backing field.

Access Modifiers and Visibility

The interface itself can have an access modifier such as public, internal, or private (for nested interfaces). Member declarations ordinarily cannot have access modifiers. Members without a body are implicitly public and abstract. With C# 8 and later, default-implemented members can be declared with access modifiers; private or protected is legal only in that context.

internal interface ILogger { void Log(string message); // implicitly public }

The visibility of the interface controls where it can be referenced. An internal interface can be implemented by any type in the same assembly, but external code cannot see it.

Implementing an Interface in a Class

A class or struct implements an interface by declaring it in its base list and providing implementations for all members that do not have default implementations. The implementing member must match the interface signature and be public unless you use explicit implementation.

public class Circle : IShape { public double Radius { get; set; } public double Area => Math.PI * Radius * Radius; public void Draw() { Console.WriteLine("Drawing a circle"); } }

The Circle class satisfies the IShape contract. The compiler enforces that every member without a default implementation is implemented. If you omit Draw(), the code will not compile.

Explicit Interface Implementation

When a class implements multiple interfaces that share a member signature, or when you want to hide an interface member from the class's public API, you can use explicit interface implementation. This is done by prefixing the member name with the interface name.

public interface IWriter { void Write(string text); } public interface ILogger { void Write(string text); } public class FileLogger : IWriter, ILogger { void IWriter.Write(string text) { // Write to file as a writer } void ILogger.Write(string text) { // Write to log as a logger } }

Explicitly implemented members are not accessible through the class instance; they can only be called through a reference to the interface. This is useful when the same method name has different meanings in different contracts.

Default Interface Members and Compatibility

Starting with C# 8, you can provide a default implementation for an interface member. This allows you to add new members to an interface without breaking existing implementers.

public interface IShape { double Area { get; } void Draw(); void Scale(double factor) { // Default implementation } }

Existing classes that implement IShape do not need to implement Scale; the default is used when Scale is called through an IShape reference. To provide custom behavior, a class can implement Scale itself. A default interface member is not inherited as an ordinary class member, so calling circle.Scale(2.0) will not compile unless the Circle class also defines Scale.

Default interface members require a runtime that supports them (for example, .NET Core 3.0 and later). On older runtimes, loading the type can throw a TypeLoadException even though the code compiles. This feature is primarily for library evolution, not for everyday application code.

Interface Inheritance and Composition

Interfaces can inherit from one or more other interfaces. This allows you to build a contract from smaller, focused pieces.

public interface IReadable { string Read(); } public interface IWritable { void Write(string data); } public interface IReadWrite : IReadable, IWritable { }

A class that implements IReadWrite must implement all abstract members from both base interfaces. This composition pattern encourages small, single-purpose interfaces rather than large monolithic ones.

Common Mistakes When Declaring Interfaces

One frequent mistake is using an interface to hold data rather than behavior. Interfaces are meant to define capabilities, not data structures. For example, an interface with only properties that represent a data record is often better modeled as a class or a record.

Another mistake is ignoring the naming convention. The I prefix is a widely accepted convention in C# and helps distinguish interfaces from classes. While not enforced by the compiler, it improves readability.

A third issue is declaring an interface with too many members. This forces every implementer to provide a large number of members, even if they are not all relevant. Prefer splitting the interface into smaller, focused contracts.

Performance and Runtime Considerations

Interface dispatch is dynamic. When you invoke a method through an interface reference, the runtime must resolve the actual implementation at runtime. This adds a small overhead compared to a direct non-virtual call on a sealed class. In most applications, this overhead is negligible, but in hot paths it can matter.

The JIT compiler can sometimes devirtualize interface calls when it can prove the concrete type, but this is not guaranteed. If you are writing performance-critical code and know the concrete type, consider using a generic constraint with where T : IShape instead of passing an interface reference directly. This allows the JIT to generate more efficient code in some scenarios.

Another runtime consideration is boxing. When a struct implements an interface and is assigned to an interface variable, it is boxed. This allocates an object on the heap and copies the struct's data. Repeated boxing in a loop can cause significant memory pressure. If you need to use interfaces with structs, be aware of this cost and consider using generic methods to avoid boxing.

Finally, default interface members introduce a runtime dependency. If you use them, ensure your target framework supports them. Otherwise, you may encounter TypeLoadException at runtime even though the code compiles successfully.

C# Interface Declaration: Syntax, Members, and Implementation | RYUSLOG DEV