Skip to content

Identity Operators

Operator Description C# Mapping
is Identity comparison object.ReferenceEquals()
is not Negated identity !object.ReferenceEquals()
is None None check x is null (reference / T \| None), (object)x is null (value type), .IsNone (Optional)
is not None Non-None check x is not null (reference / T \| None), (object)x is not null (value type), .IsSome (Optional)

is None Never Runs User Code

is None / is not None are a reference null check on every type — a Sharpy class with __eq__, a .NET class with an overloaded ==, str, a collection. They lower to the C# null pattern, which calls no operator. == is the spelling that runs __eq__ (or a .NET operator ==):

class Anything:
    def __eq__(self, other: object) -> bool:
        return True

    def __hash__(self) -> int:
        return 0


def main() -> None:
    a: Anything | None = Anything()
    print(a is None)      # False — a holds a live object
    print(a == None)      # True — `==` runs __eq__, as in Python
    b: Anything | None = None
    print(b is None)      # True
    print(b == None)      # True — a None left operand is never dereferenced

A .NET type whose == treats a live object as null (Unity's destroyed Object) answers False to is None; see .NET Interop for the spelling that asks its ==.

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.