C# Implicit and Explicit Conversion Operators

This article covers how to implement user-defined type conversions in C# using the implicit and explicit keywords. You will learn the syntax, key differences, best practices for safety, and clear code examples demonstrating how to enable seamless conversion between custom types and standard .NET types.


Understanding Conversion Operators

In C#, user-defined conversion operators allow custom classes or structs to be converted to and from other types. These operators must always be declared as public static.

C# provides two keywords to specify how the conversion occurs:

  • implicit: Invoked automatically by the compiler without requiring a cast syntax. Use this when the conversion is safe, does not lose data, and will never throw an exception.
  • explicit: Requires a cast expression (e.g., (TargetType)source). Use this when the conversion may lose precision, lose information, or potentially throw an exception.

Defining an Implicit Conversion Operator

An implicit conversion improves code readability by eliminating repetitive casting when the conversion is intuitive and inherently safe.

Syntax

public static implicit operator TargetType(SourceType source)
{
    // conversion logic
}

Example

Converting a custom Currency object to a decimal is safe and loss-free:

public readonly struct Currency
{
    public decimal Amount { get; }

    public Currency(decimal amount)
    {
        Amount = amount;
    }

    // Implicit conversion from Currency to decimal
    public static implicit operator decimal(Currency currency)
    {
        return currency.Amount;
    }
}

// Usage
Currency wallet = new Currency(49.99m);
decimal value = wallet; // No cast required

Defining an Explicit Conversion Operator

An explicit conversion warns the developer that an operation carries risk, such as truncation or a potential runtime exception.

Syntax

public static explicit operator TargetType(SourceType source)
{
    // conversion logic
}

Example

Converting an int to a custom Digit type (which only accepts values from 0 to 9) can fail, making an explicit operator necessary:

using System;

public readonly struct Digit
{
    public byte Value { get; }

    public Digit(byte value)
    {
        if (value > 9)
        {
            throw new ArgumentOutOfRangeException(nameof(value), "Value must be between 0 and 9.");
        }
        Value = value;
    }

    // Explicit conversion from int to Digit
    public static explicit operator Digit(int number)
    {
        if (number < 0 || number > 9)
        {
            throw new InvalidCastException($"Cannot convert {number} to a single Digit.");
        }
        return new Digit((byte)number);
    }

    // Implicit conversion back to int is safe
    public static implicit operator int(Digit digit) => digit.Value;
}

// Usage
int validNumber = 7;
Digit d = (Digit)validNumber; // Explicit cast required

int invalidNumber = 25;
// Throws InvalidCastException at runtime:
// Digit failedDigit = (Digit)invalidNumber; 

Rules and Constraints

When implementing conversion operators in C#, adhere to the following language rules:

  • Type Ownership: Either the parameter type (source) or the return type (target) must be the enclosing type where the operator is declared.
  • No Redundant Conversions: You cannot define conversions between a base class and a derived class, as standard inheritance-based conversions already handle those scenarios.
  • No Interface Conversions: You cannot define conversion operators to or from interface types.
  • No object Conversions: Conversions directly to or from System.Object are disallowed.
  • Unique Signatures: You cannot define both an implicit and an explicit operator for the same source and target types.