Skip to content

Identity Operators

Operator Description C# Mapping
is Identity comparison object.ReferenceEquals()
is not Negated identity !object.ReferenceEquals()
is None None check == null (nullable/reference), .IsNone (Optional)
is not None Non-None check != null (nullable/reference), !.IsNone (Optional)

is Is Not a Type Test

is compares references, never types. Writing a type name on the right — x is Dog — is rejected with SPY0349; it was a type test in earlier versions of Sharpy, which silently diverged from CPython (a is Dog in CPython is an identity comparison against the class object, so it is False). Use isinstance() to test a value's type.

class Dog:
    name: str

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


def main() -> None:
    x: object = Dog("Rex")
    if x is Dog:          # SPY0349: 'is' compares references, not types
        print("never")
error[SPY0349]: 'is' compares references, not types. Use 'isinstance(x, Dog)' to test a value's type.

The isinstance() spelling compiles and narrows x to Dog inside the branch:

class Dog:
    name: str

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


def main() -> None:
    x: object = Dog("Rex")
    if isinstance(x, Dog):
        print(x.name)     # prints: Rex

The identity spellings in the table above are unaffected: x is None, x is not None and x is y between two references all keep working, because none of them names a type.

Value-Type Boxing Warning

Using is or is not with value types (e.g., int, bool, float, structs) emits a compile-time warning (SPY0465) because identity comparison on value types is meaningless in .NET: each operand is boxed into a separate heap object, so the result is always False. Use == or != for value equality instead.

x: int = 1
y: int = 1
x is y    # SPY0465 warning — always False due to boxing
x == y    # correct — value equality

Implementation - ✅ Native for None checks; 🔄 Lowered for general identity. - ⚠️ SPY0465 warning when both operands are value types.