Skip to content

Interface Default Methods

Interfaces can provide default implementations for methods. Implementing types inherit the default unless they provide their own implementation.

Basic Default Implementations

interface ILogger:
    def log(self, message: str):
        """Log a message. Must be implemented."""
        ...

    def log_info(self, message: str):
        """Log an info message. Has default implementation."""
        self.log(f"[INFO] {message}")

    def log_error(self, message: str):
        """Log an error message. Has default implementation."""
        self.log(f"[ERROR] {message}")

class ConsoleLogger(ILogger):
    # Must implement abstract method
    def log(self, message: str):
        print(message)

    # Inherits log_info and log_error defaults
    # Can optionally override them

class FileLogger(ILogger):
    path: str

    def __init__(self, path: str):
        self.path = path

    def log(self, message: str):
        # Write to file
        pass

    # Override default to add timestamp
    def log_error(self, message: str):
        self.log(f"[ERROR {datetime.now()}] {message}")

Calling Other Interface Methods

Default implementations can call other methods defined in the same interface (including methods inherited by the interface through parent interfaces, without using super()):

interface IValidator:
    def validate(self, value: str) -> bool:
        """Core validation logic. Must be implemented."""
        ...

    def is_valid(self, value: str) -> bool:
        """Check validity, returning boolean."""
        return self.validate(value)

    def validate_or_raise(self, value: str) -> None:
        """Validate and raise if invalid."""
        if not self.validate(value):
            raise ValueError(f"Invalid value: {value}")

    def validate_all(self, values: list[str]) -> bool:
        """Validate multiple values."""
        for value in values:
            if not self.validate(value):
                return False
        return True

Conflict Resolution: Base Class vs Interface

When a class inherits the same method signature from both a base class and an interface, Sharpy follows C# resolution rules:

Rule: Base class takes precedence over interface default implementations.

interface IGreeter:
    def greet(self) -> str:
        return "Hello from interface"

class BaseGreeter:
    def greet(self) -> str:
        return "Hello from base class"

class MyGreeter(BaseGreeter, IGreeter):
    # No override needed - inherits from BaseGreeter
    pass

g = MyGreeter()
print(g.greet())  # "Hello from base class"

Multiple Interface Conflicts

When multiple interfaces provide defaults for the same method, the implementing class must provide its own implementation:

interface IA:
    def method(self) -> str:
        return "A"

interface IB:
    def method(self) -> str:
        return "B"

class C(IA, IB):
    # ❌ ERROR if omitted: ambiguous default implementations
    # ✅ Must provide explicit implementation
    def method(self) -> str:
        return "C"

When to Use Interfaces vs Abstract Classes

With default implementations available in interfaces, the choice between interfaces and abstract classes may seem unclear. Here are the key distinctions:

Feature Interface Abstract Class
Fields (state) ❌ Cannot have fields ✅ Can have fields
Multiple inheritance ✅ A class can implement multiple interfaces ❌ A class can only extend one class
Constructors ❌ No constructors ✅ Can have constructors
Access modifiers on members ❌ All members implicitly public ✅ Can have protected/private members
Default implementations ✅ Supported (C# 8.0+) ✅ Supported

Guidelines:

  • Use interfaces when defining a contract ("what can this do?") without requiring shared state
  • Use abstract classes when you need shared state (fields) or protected members across a family of related types
  • Use interfaces when a type needs to satisfy multiple contracts
  • Use abstract classes for "is-a" relationships with shared implementation
# Interface: defines capability without state
interface ISerializable:
    def serialize(self) -> str: ...

# Abstract class: shared state and partial implementation
class Entity:
    id: int                    # Shared field
    created_at: datetime       # Shared field

    def __init__(self, id: int):
        self.id = id
        self.created_at = datetime.now()

    @abstract
    def validate(self) -> bool:
        ...                    # Subclasses must implement

Implementation - ✅ Native - Direct mapping to C# default interface methods (C# 8.0+) and explicit interface implementation.

See Also

  • Interfaces - Interface basics, generic interfaces, and inheritance
  • Inheritance - Class inheritance and super()
  • Decorators - @abstract, @virtual, @override