C# Interface Method: Declaration and Implementation
c# interface method: Learn how to declare, implement, and use C# interface methods, including explicit implementation, default methods, and common pitfalls.
c# interface method requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
An interface method in C# declares a contract without providing an implementation. When a class or struct implements an interface, it must supply the body for each method unless the method has a default implementation. This separation lets code depend on an abstraction rather than a specific type. Here is the basic syntax:
public interface ILogger { void Log(string message); }
This interface declares a single method Log. Any type that implements ILogger must provide a concrete Log method. The method is implicitly public and abstract; you cannot add an access modifier or a body in the interface unless you use a default implementation (C# 8 and later).
Declaring an Interface Method
Interface methods are public by default. You cannot specify public, private, protected, or any other access modifier on an interface method declaration. Attempting to do so produces a compile-time error. The method signature includes the return type, name, and parameters, but no implementation.
public interface IRepository { Task<Customer> GetByIdAsync(int id); Task SaveAsync(Customer customer); }
Interface methods can be instance methods only. Static methods were not allowed in interfaces until C# 11, which introduced static abstract and static virtual members. For most applications, interface methods are instance methods that describe behavior.
Implementing an Interface Method in a Class
When a class implements an interface, it must provide a public method that matches the interface signature. The method can have any implementation logic, but it must be accessible from the interface reference.
public class FileLogger : ILogger { public void Log(string message) { File.AppendAllText("log.txt", message + Environment.NewLine); } }
Here, FileLogger implements ILogger by writing to a file. The method is public and matches the interface signature. If the class does not implement the method, the compiler reports an error. A class can implement multiple interfaces, and each interface method must be implemented unless it has a default implementation.
Explicit Interface Implementation
Sometimes you need to implement a method that is only visible when the object is accessed through the interface reference. This is called explicit interface implementation. It is useful when two interfaces declare a method with the same name and signature, or when you want to hide the method from the class's public API.
public interface IWriter { void Write(string text); } public interface IPrinter { void Write(string text); } public class Document : IWriter, IPrinter { void IWriter.Write(string text) { /* write to file */ } void IPrinter.Write(string text) { /* print to console */ } }
In this example, Document implements both interfaces explicitly. You must cast the instance to the specific interface to call the method:
var doc = new Document(); ((IWriter)doc).Write("Hello"); ((IPrinter)doc).Write("Hello");
Explicit implementation is also useful when you want to prevent callers from invoking a method directly on the concrete type, forcing them to go through the interface. This can reduce accidental misuse.
Default Interface Methods
C# 8 introduced default interface methods, allowing an interface to provide a body for a method. Implementing types can choose to override the default, but they are not required to. This feature is primarily for API evolution—adding a method to an existing interface without breaking existing implementers.
public interface ILogger { void Log(string message); void LogError(string message) { Log($"ERROR: {message}"); } }
Any class that implements ILogger must still provide Log, but LogError is optional. If the class does not implement it, the default body is used. This is a significant departure from earlier C# versions, where interface methods were purely abstract.
Default methods are not inherited like class methods. When you call LogError on a concrete instance, the compiler uses the most specific implementation: the class method if defined, otherwise the interface default. This behavior can be confusing, so use default interface methods sparingly and document them clearly.
Interface Methods and Polymorphism
Interface methods enable polymorphism by letting you treat different types uniformly through the interface reference. This is central to dependency injection, unit testing, and many design patterns.
public class NotificationService { private readonly ILogger _logger; public NotificationService(ILogger logger) { _logger = logger; } public void Send(string message) { _logger.Log(message); } }
Here, NotificationService depends on ILogger, not on a concrete logger. You can pass any implementation—file, console, database—without changing the service. The method call _logger.Log(message) is dispatched at runtime to the actual implementation.
This dispatch is virtual. The runtime looks up the method implementation based on the object's type. The cost is slightly higher than a direct non-virtual call, but the JIT compiler can often optimize interface calls in hot paths, especially when the concrete type is known. For most applications, the flexibility outweighs the negligible performance overhead.
Common Pitfalls with Interface Methods
One frequent mistake is forgetting to implement all interface members. The compiler catches this, but the error message can be cryptic if the interface has many methods. Implementing a method with a different signature—such as changing parameter types or return type—also fails, because the compiler requires an exact match.
Another pitfall is method hiding. If a class implements an interface method and also defines a method with the same name but different parameters, the interface method is hidden. This can lead to unexpected behavior when calling through the interface versus the concrete type.
public class MyLogger : ILogger { public void Log(string message) { /* ... */ } public void Log(string message, bool verbose) { /* ... */ } }
This is valid, but the interface call ILogger.Log(message) always invokes the single-parameter version. The overload is not accessible through the interface. Keep interface implementations straightforward and avoid overloads that could confuse callers.
Multiple interface inheritance can also cause ambiguity. If two interfaces declare a method with the same signature and a class implements both, you must use explicit implementation to distinguish them. Without explicit implementation, the compiler will not know which method to use, and you will get a compile error.
Performance and Runtime Considerations
Interface method calls are virtual calls. The runtime must determine the concrete method to execute based on the object's type. This involves a lookup in the type's method table, which is slightly more expensive than a direct call. However, modern JIT compilers can often devirtualize interface calls when the concrete type is known at the call site, especially in tight loops or with sealed types.
If performance is critical and you are calling an interface method millions of times, consider whether the abstraction is necessary. In many cases, the overhead is negligible, but you should profile if you suspect a bottleneck. Also, be aware that default interface methods can add an extra level of dispatch because the runtime may need to check whether the implementing type overrides the default.
Memory usage is not directly affected by interface methods themselves. The method table for a type includes entries for all interface methods it implements, which adds a small amount of metadata. This is usually not a concern unless you have thousands of types.
When designing interfaces, keep the method set small and focused. Each method adds implementation burden for every implementing type. A large interface with many methods is harder to implement and maintain. Consider splitting it into smaller, role-specific interfaces (interface segregation principle).