There's no simple answer for this question. Anyone who says always use one or the other is giving you poor advice, in my opinion.

There are actually several different methods you can call to compare object instances. Given two object instances a and b, you could write:

  • Object.Equals(a,b)
  • Object.ReferenceEquals(a,b)
  • a.Equals(b)
  • a == b

These could all do different things!

Object.Equals(a,b) will (by default) perform reference equality comparison on reference types and bitwise comparison on value types. From the MSDN documentation:

The default implementation of Equals supports reference equality for reference types, and bitwise equality for value types. Reference equality means the object references that are compared refer to the same object. Bitwise equality means the objects that are compared have the same binary representation.

Note that a derived type might override the Equals method to implement value equality. Value equality means the compared objects have the same value but different binary representations.

Note the last paragraph above ... we'll discuss this a bit later.

Object.ReferenceEquals(a,b) performs reference equality comparison only. If the types passed are boxed value types, the result is always false.

a.Equals(b) calls the virtual instance method of Object, which the type of a could override to do anything it wants. The call is performed using virtual dispatch, so the code that runs depends on the runtime type of a.

a == b invokes the static overloaded operator of the **compile-time type* of a. If the implementation of that operator invokes instance methods on either a or b, it may also depend on the runtime types of the parameters. Since the dispatch is based on the types in the expression, the following may yield different results:

Frog aFrog = new Frog();
Frog bFrog = new Frog();
Animal aAnimal = aFrog;
Animal bAnimal = bFrog;
// not necessarily equal...
bool areEqualFrogs = aFrog == bFrog;
bool areEqualAnimals = aAnimal = bAnimal;

So, yes, there is vulnerability for check for nulls using operator ==. In practice, most types do not overload == - but there's never a guarantee.

The instance method Equals() is no better here. While the default implementation performs reference/bitwise equality checks, it is possible for a type to override the Equals() member method, in which case this implementation will be called. A user supplied implementation could return whatever it wants, even when comparing to null.

But what about the static version of Object.Equals() you ask? Can this end up running user code? Well, it turns out that the answer is YES. The implementation of Object.Equals(a,b) expands to something along the lines of:

((object)a == (object)b) || (a != null && b != null && a.Equals(b))

You can try this for yourself:

class Foo {
    public override bool Equals(object obj) { return true; }  }

var a = new Foo();
var b = new Foo();
Console.WriteLine( Object.Equals(a,b) );  // outputs "True!"

As a consequence, it's possible for the statement: Object.Equals(a,b) to run user code when neither of the types in the call are null. Note that Object.Equals(a,b) does not call the instance version of Equals() when either of the arguments is null.

In short, the kind of comparison behavior you get can vary significantly, depending on which method you choose to call. One comment here, however: Microsoft doesn't officially document the internal behavior of Object.Equals(a,b). If you need an iron clad gaurantee of comparing a reference to null without any other code running, you want Object.ReferenceEquals():

Object.ReferenceEquals(item, null);

This method makes the intent extremently clear - you are specifically expecting the result to be the comparison of two references for reference equality. The benefit here over using something like Object.Equals(a,null), is that it's less likely that someone will come along later and say:

"Hey, this is awkward, let's replace it with: a.Equals(null) or a == null

which potentially may be different.

Let's inject some pragmatism here, however. So far we've talked about the potential for different modalities of comparison to yield different results. While this is certainly the case, there are certain types where it's safe to write a == null. Built-in .NET classes like String and Nullable<T> have well defined semantics for comparison. Furthermore, they are sealed - preventing any change to their behavior through inheritance. The following is quite common (and correct):

string s = ...
if( s == null ) { ... }

It's unnecessary (and ugly) to write:

if( ReferenceEquals(s,null) ) { ... }

So in certain limited cases, using == is safe, and appropriate.

Answer from LBushkin on Stack Overflow
🌐
CodeQL
codeql.github.com › codeql-query-help › csharp › cs-null-argument-to-equals
Null argument to Equals(object) — CodeQL query help documentation
In the following example, IsNull will throw a NullReferenceException when o is null. class Bad { bool IsNull(object o) => o.Equals(null); }
🌐
Reddit
reddit.com › r/csharp › c# — ‘is null’ vs ‘== null’
r/csharp on Reddit: C# — ‘is null’ vs ‘== null’
January 22, 2024 - Because equality is transitive, if a == null and b == null, then a == b. ... It's tricky. The C# standard defines the null literal as simply a reference that doesn't refer to any object.
Top answer
1 of 7
88

There's no simple answer for this question. Anyone who says always use one or the other is giving you poor advice, in my opinion.

There are actually several different methods you can call to compare object instances. Given two object instances a and b, you could write:

  • Object.Equals(a,b)
  • Object.ReferenceEquals(a,b)
  • a.Equals(b)
  • a == b

These could all do different things!

Object.Equals(a,b) will (by default) perform reference equality comparison on reference types and bitwise comparison on value types. From the MSDN documentation:

The default implementation of Equals supports reference equality for reference types, and bitwise equality for value types. Reference equality means the object references that are compared refer to the same object. Bitwise equality means the objects that are compared have the same binary representation.

Note that a derived type might override the Equals method to implement value equality. Value equality means the compared objects have the same value but different binary representations.

Note the last paragraph above ... we'll discuss this a bit later.

Object.ReferenceEquals(a,b) performs reference equality comparison only. If the types passed are boxed value types, the result is always false.

a.Equals(b) calls the virtual instance method of Object, which the type of a could override to do anything it wants. The call is performed using virtual dispatch, so the code that runs depends on the runtime type of a.

a == b invokes the static overloaded operator of the **compile-time type* of a. If the implementation of that operator invokes instance methods on either a or b, it may also depend on the runtime types of the parameters. Since the dispatch is based on the types in the expression, the following may yield different results:

Frog aFrog = new Frog();
Frog bFrog = new Frog();
Animal aAnimal = aFrog;
Animal bAnimal = bFrog;
// not necessarily equal...
bool areEqualFrogs = aFrog == bFrog;
bool areEqualAnimals = aAnimal = bAnimal;

So, yes, there is vulnerability for check for nulls using operator ==. In practice, most types do not overload == - but there's never a guarantee.

The instance method Equals() is no better here. While the default implementation performs reference/bitwise equality checks, it is possible for a type to override the Equals() member method, in which case this implementation will be called. A user supplied implementation could return whatever it wants, even when comparing to null.

But what about the static version of Object.Equals() you ask? Can this end up running user code? Well, it turns out that the answer is YES. The implementation of Object.Equals(a,b) expands to something along the lines of:

((object)a == (object)b) || (a != null && b != null && a.Equals(b))

You can try this for yourself:

class Foo {
    public override bool Equals(object obj) { return true; }  }

var a = new Foo();
var b = new Foo();
Console.WriteLine( Object.Equals(a,b) );  // outputs "True!"

As a consequence, it's possible for the statement: Object.Equals(a,b) to run user code when neither of the types in the call are null. Note that Object.Equals(a,b) does not call the instance version of Equals() when either of the arguments is null.

In short, the kind of comparison behavior you get can vary significantly, depending on which method you choose to call. One comment here, however: Microsoft doesn't officially document the internal behavior of Object.Equals(a,b). If you need an iron clad gaurantee of comparing a reference to null without any other code running, you want Object.ReferenceEquals():

Object.ReferenceEquals(item, null);

This method makes the intent extremently clear - you are specifically expecting the result to be the comparison of two references for reference equality. The benefit here over using something like Object.Equals(a,null), is that it's less likely that someone will come along later and say:

"Hey, this is awkward, let's replace it with: a.Equals(null) or a == null

which potentially may be different.

Let's inject some pragmatism here, however. So far we've talked about the potential for different modalities of comparison to yield different results. While this is certainly the case, there are certain types where it's safe to write a == null. Built-in .NET classes like String and Nullable<T> have well defined semantics for comparison. Furthermore, they are sealed - preventing any change to their behavior through inheritance. The following is quite common (and correct):

string s = ...
if( s == null ) { ... }

It's unnecessary (and ugly) to write:

if( ReferenceEquals(s,null) ) { ... }

So in certain limited cases, using == is safe, and appropriate.

2 of 7
6

if (Equals(item, null)) is no more robust than if (item == null), and I find it more confusing to boot.

🌐
Medium
medium.com › @AlexanderObregon › javas-objects-equals-method-explained-3a84c963edfa
Java’s Objects.equals() Method Explained | Medium
4 days ago - If one object is null and the other is not, the method returns false, as a null object cannot be equal to a non-null object.
🌐
Microsoft Learn
learn.microsoft.com › en-us › dotnet › api › system.object.equals
Object.Equals Method (System) | Microsoft Learn
The static Equals(Object, Object) method indicates whether two objects, objA and objB, are equal. It also enables you to test objects whose value is null for equality. It compares objA and objB for equality as follows:
🌐
Medium
medium.com › @erickgallani › the-difference-between-null-check-with-is-instead-of-in-c-ed3d955a639e
The difference between null check with ‘is’ instead of ‘==’ in C# | by Erick Gallani | Medium
June 27, 2022 - I found out that there are a lot of people that think is and is not (introduced in C# 9) for null checks is just syntax sugar for the equality or inequality operator (== and !=).
🌐
.NET Fiddle
dotnetfiddle.net › gESLzO
.Equals() fails when object is null | C# Online Compiler | .NET Fiddle
.Equals() fails when object is null | Test your C# code online with .NET Fiddle code editor.
Find elsewhere
🌐
Errorprone
errorprone.info › bugpattern › EqualsNull
EqualsNull
The contract of Object.equals() states that for any non-null reference value x, x.equals(null) should return false. Thus code such as · if (x.equals(null)) { ... } either returns false, or throws a NullPointerException if x is null. The nested block may never execute.
🌐
Meziantou's blog
meziantou.net › null-check-in-csharp.htm
Different ways to check if a value is null in C# - Meziantou's blog
June 7, 2021 - In this post, I describe the different ways to check a value is null in C# and how they differ.
🌐
Coderanch
coderanch.com › t › 523760 › java › null-null-java
null==something vs something==null in java (Beginning Java forum at Coderanch)
The long answer is that this is a remains from the old C days where everything could be treated as a boolean. If you forgot one of the = signs you would get an assignment. With something = null that assignment would succeed and the result would be equal to false.
🌐
TutorialsPoint
tutorialspoint.com › how-to-check-if-a-variable-is-null-in-c-cplusplus
How to check if a variable is NULL in C/C++?
In C/C++, there is no special method for comparing NULL values. We can use if statements to check whether a variable is null or not.Here, we will attempt to open a file in read mode that does not exist on the system. In this case, the function will r
🌐
C# Corner
c-sharpcorner.com › article › story-of-equality-in-net-part-two
Understand Virtual Object.Equals, Static Object.Equals and Reference.Equals in Object Class
May 1, 2024 - Objects provide a static Equals method, which can be used when there is a chance that one or both of the parameters can be null, other than that, it behaves identically to the virtual Object.
Top answer
1 of 16
213

They're two completely different things. == compares the object reference, if any, contained by a variable. .equals() checks to see if two objects are equal according to their contract for what equality means. It's entirely possible for two distinct object instances to be "equal" according to their contract. And then there's the minor detail that since equals is a method, if you try to invoke it on a null reference, you'll get a NullPointerException.

For instance:

class Foo {
    private int data;

    Foo(int d) {
        this.data = d;
    }

    @Override
    public boolean equals(Object other) {
        if (other == null || other.getClass() != this.getClass()) {
           return false;
        }
        return ((Foo)other).data == this.data;
    }

    /* In a real class, you'd override `hashCode` here as well */
}

Foo f1 = new Foo(5);
Foo f2 = new Foo(5);
System.out.println(f1 == f2);
// outputs false, they're distinct object instances

System.out.println(f1.equals(f2));
// outputs true, they're "equal" according to their definition

Foo f3 = null;
System.out.println(f3 == null);
// outputs true, `f3` doesn't have any object reference assigned to it

System.out.println(f3.equals(null));
// Throws a NullPointerException, you can't dereference `f3`, it doesn't refer to anything

System.out.println(f1.equals(f3));
// Outputs false, since `f1` is a valid instance but `f3` is null,
// so one of the first checks inside the `Foo#equals` method will
// disallow the equality because it sees that `other` == null
2 of 16
52

In addition to the accepted answer (https://stackoverflow.com/a/4501084/6276704):

Since Java 1.7, if you want to compare two Objects which might be null, I recommend this function:

Objects.equals(onePossibleNull, twoPossibleNull)

java.util.Objects

This class consists of static utility methods for operating on objects. These utilities include null-safe or null-tolerant methods for computing the hash code of an object, returning a string for an object, and comparing two objects.

Since: 1.7

🌐
Reddit
reddit.com › r/csharp › [deleted by user]
Object.ReferenceEquals, != / == null, or "is null"? (and why?)
March 24, 2017 - I personally use (or used to, at least, before C# 7) (object)x == null. ... It is not equal to the == or != operators, because those could've been overloaded.
🌐
Microsoft Learn
learn.microsoft.com › en-us › dotnet › api › system.nullable-1.equals
Nullable .Equals(Object) Method (System) | Microsoft Learn
Console.Write("1) nullInt1 and nullInt2 "); if (nullInt1.Equals(nullInt2)) Console.Write("are"); else Console.Write("are not"); Console.WriteLine(" equal."); // Determine if a nullable of System.Int32 and an object // are equal. The object contains the boxed value of the // nullable object.
Top answer
1 of 11
32

This behaviour is defined in the C# specification (ECMA-334) in section 14.2.7 (I have highlighted the relevant part):

For the relational operators

< > <= >=

a lifted form of an operator exists if the operand types are both non-nullable value types and if the result type is bool. The lifted form is constructed by adding a single ? modifier to each operand type. The lifted operator produces the value false if one or both operands are null. Otherwise, the lifted operator unwraps the operands and applies the underlying operator to produce the bool result.

In particular, this means that the usual laws of relations don't hold; x >= y does not imply !(x < y).

Gory details

Some people have asked why the compiler decides that this is a lifted operator for int? in the first place. Let's have a look. :)

We start with 14.2.4, 'Binary operator overload resolution'. This details the steps to follow.

  1. First, the user-defined operators are examined for suitability. This is done by examining the operators defined by the types on each side of >=... which raises the question of what the type of null is! The null literal actually doesn't have any type until given one, it's simply the "null literal". By following the directions under 14.2.5 we discover there are no operators suitable here, since the null literal doesn't define any operators.

  2. This step instructs us to examine the set of predefined operators for suitability. (Enums are also excluded by this section, since neither side is an enum type.) The relevant predefined operators are listed in sections 14.9.1 to 14.9.3, and they are all operators upon primitive numeric types, along with the lifted versions of these operators (note that strings operators are not included here).

  3. Finally, we must perform overload resolution using these operators and the rules in 14.4.2.

Actually performing this resolution would be extremely tedious, but luckily there is a shortcut. Under 14.2.6 there is an informative example given of the results of overload resolution, which states:

...consider the predefined implementations of the binary * operator:

int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
void operator *(long x, ulong y);
void operator *(ulong x, long y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);

When overload resolution rules (§14.4.2) are applied to this set of operators, the effect is to select the first of the operators for which implicit conversions exist from the operand types.

Since both sides are null we can immediately throw out all unlifted operators. This leaves us with the lifted numeric operators on all primitive numeric types.

Then, using the previous information, we select the first of the operators for which an implicit conversion exists. Since the null literal is implicitly convertible to a nullable type, and a nullable type exists for int, we select the first operator from the list, which is int? >= int?.

2 of 11
30

A number of answers appeal to the spec. In what turns out to be an unusual turn of events, the C# 4 spec does not justify the behaviour specifically mentioned of comparison of two null literals. In fact, a strict reading of the spec says that "null == null" should give an ambiguity error! (This is due to an editing error made during the cleanup of the C# 2 specification in preparation for C# 3; it is not the intention of the spec authors to make this illegal.)

Read the spec carefully if you don't believe me. It says that there are equality operators defined on int, uint, long, ulong, bool, decimal, double, float, string, enums, delegates and objects, plus the lifted-to-nullable versions of all the value type operators.

Now, immediately we have a problem; this set is infinitely large. In practice we do not form the infinite set of all operators on all possible delegate and enum types. The spec needs to be fixed up here to note that the only operators on enum and delegate types which are added to the candidate sets are those of enum or delegate types that are the types of either argument.

Let's therefore leave enum and delegate types out of it, since neither argument has a type.

We now have an overload resolution problem; we must first eliminate all the inapplicable operators, and then determine the best of the applicable operators.

Clearly the operators defined on all the non-nullable value types are inapplicable. That leaves the operators on the nullable value types, and string, and object.

We can now eliminate some for reasons of "betterness". The better operator is the one with the more specific types. int? is more specific than any of the other nullable numeric types, so all of those are eliminated. String is more specific than object, so object is eliminated.

That leaves equality operators for string, int? and bool? as the applicable operators. Which one is the best? None of them is better than the other. Therefore this should be an ambiguity error.

For this behaviour to be justified by the spec we are going to have to emend the specification to note that "null == null" is defined as having the semantics of string equality, and that it is the compile-time constant true.

I actually just discovered this fact yesterday; how odd that you should ask about it.

To answer the questions posed in other answers about why null >= null gives a warning about comparisons to int? -- well, apply the same analysis as I just did. The >= operators on non-nullable value types are inapplicable, and of the ones that are left, the operator on int? is the best. There is no ambiguity error for >= because there is no >= operator defined on bool? or string. The compiler is correctly analyzing the operator as being comparison of two nullable ints.

To answer the more general question about why operators on null values (as opposed to literals) have a particular unusual behaviour, see my answer to the duplicate question. It clearly explains the design criteria that justify this decision. In short: operations on null should have the semantics of operations on "I don't know". Is a quantity you don't know greater than or equal to another quantity you don't know? The only sensible answer is "I don't know!" But we need to turn that into a bool, and the sensible bool is "false". But when comparing for equality, most people think that null should be equal to null even though comparing two things that you don't know for equality should also result in "I don't know". This design decision is the result of trading off many undesirable outcomes against one another to find the least bad one that makes the feature work; it does make the language somewhat inconsistent, I agree.