C# Partial Methods: How They Work and Constraints

Partial methods in C# allow developers to declare a method signature in one part of a partial type and optionally implement it in another part or source file. Primarily utilized in code generation and source generators, they provide clean, compile-time hooks for extending generated logic without introducing runtime overhead. This article explains how partial methods work, the distinction between optional and required partial methods, and the strict constraints that govern their implementation.

How Partial Methods Work

A partial method consists of two parts: the definition (the method signature) and the implementation (the method body). Both parts must reside within a partial type, such as a partial class, partial struct, or partial interface.

Depending on how they are declared, partial methods function in one of two modes:

1. Optional Partial Methods (Classic)

Introduced in C# 3.0, optional partial methods act as lightweight hooks. If a defining declaration is present but no implementing declaration is provided:

  • The C# compiler removes the method signature during compilation.
  • All calls to the method are completely eliminated from the generated Intermediate Language (IL).
  • Any arguments evaluated solely for the method call are also stripped, avoiding performance penalties.
public partial class OrderProcessor
{
    // Defining declaration
    partial void OnOrderProcessed(int orderId);

    public void ProcessOrder(int orderId)
    {
        // If OnOrderProcessed is not implemented, this call is erased at compile time
        OnOrderProcessed(orderId);
    }
}

2. Extended Partial Methods (Required)

Starting with C# 9.0 to support Roslyn Source Generators, partial methods can be extended to require an implementation. If an accessibility modifier (such as public, internal, or an explicit private) is provided on the defining signature:

  • The method must have an implementation somewhere within the partial type.
  • The method is not erased at compile time.
  • It can return values and participate in standard object-oriented patterns.
public partial class UserValidator
{
    // Defining declaration (requires an implementation because it has an access modifier)
    public partial bool ValidateUsername(string username);
}

public partial class UserValidator
{
    // Implementing declaration
    public partial bool ValidateUsername(string username)
    {
        return !string.IsNullOrWhiteSpace(username);
    }
}

Constraints on Partial Methods

The C# compiler enforces specific rules depending on whether the partial method is optional or required.

Universal Constraints

  • Enclosing Types: The method must be declared inside a partial class, partial struct, partial interface, or partial record.
  • Matching Signatures: The defining and implementing declarations must match exactly in method name, parameter types, generic constraints, and parameter modifiers (ref, in).
  • Static Modifiers: A partial method can be static, but both declarations must include the static modifier.
  • Delegate Creation: You cannot create a delegate pointing to an optional partial method that has no implementation.

Constraints on Optional Partial Methods

If a partial method does not specify an explicit access modifier, it defaults to optional mode and is subjected to the following limitations:

  • Return Type: Must return void.
  • Accessibility: Cannot have explicit access modifiers (they are implicitly private).
  • Parameters: Cannot have out parameters, because the compiler cannot guarantee the variable would be assigned if the method call is removed.
  • Inheritance Modifiers: Cannot use virtual, abstract, override, new, or sealed.
  • Extern Modifier: Cannot be marked as extern.

Constraints on Extended (Required) Partial Methods

If a partial method includes an explicit access modifier, it is considered required:

  • Mandatory Implementation: If a definition exists with an access modifier, failing to provide an implementation generates a compile-time error.
  • Return Type Flexibility: Can return any type, not just void.
  • Parameters: out parameters are permitted because the method is guaranteed to exist and execute.
  • Inheritance Modifiers: Can be marked with virtual, override, abstract, or sealed, provided the access modifiers and context allow it.