Muistiinpano
Tämän sivun käyttö edellyttää valtuutusta. Voit yrittää kirjautua sisään tai vaihtaa hakemistoa.
Tämän sivun käyttö edellyttää valtuutusta. Voit yrittää vaihtaa hakemistoa.
The == (equality) and != (inequality) operators check if their operands are equal or not. Value types are equal when their contents are equal. Reference types are equal when the two variables refer to the same storage.
The C# language reference documents the most recently released version of the C# language. It also contains initial documentation for features in public previews for the upcoming language release.
The documentation identifies any feature first introduced in the last three versions of the language or in current public previews.
Tip
To find when a feature was first introduced in C#, consult the article on the C# language version history.
You can use the is pattern matching operator as an alternative to an == test when you test against a constant value. The is operator uses the default equality semantics for all value and reference types.
Equality operator ==
The equality operator == returns true if its operands are equal, false otherwise.
Value types equality
Operands of the built-in value types are equal if their values are equal:
int a = 1 + 2 + 3;
int b = 6;
Console.WriteLine(a == b); // output: True
char c1 = 'a';
char c2 = 'A';
Console.WriteLine(c1 == c2); // output: False
Console.WriteLine(c1 == char.ToLower(c2)); // output: True
Note
For the ==, <, >, <=, and >= operators, if any of the operands isn't a number (Double.NaN or Single.NaN), the result of operation is false. That condition means that the NaN value isn't greater than, less than, or equal to any other double (or float) value, including NaN. For more information and examples, see the Double.NaN or Single.NaN reference article.
Two operands of the same enum type are equal if the corresponding values of the underlying integral type are equal.
User-defined struct types don't support the == operator by default. To support the == operator, a user-defined struct must overload it.
C# tuples have built-in support for the == and != operators. For more information, see the Tuple equality section of the Tuple types article.
Reference types equality
By default, reference-type operands, excluding records, are equal if they refer to the same object:
public class ReferenceTypesEquality
{
public class MyClass
{
private int id;
public MyClass(int id) => this.id = id;
}
public static void Main()
{
var a = new MyClass(1);
var b = new MyClass(1);
var c = a;
Console.WriteLine(a == b); // output: False
Console.WriteLine(a == c); // output: True
}
}
As the preceding example shows, user-defined reference types support the == operator by default. However, a reference type can overload the == operator. If a reference type overloads the == operator, use the Object.ReferenceEquals method to check if two references of that type refer to the same object.
Record types equality
Record types support the == and != operators that by default provide value equality semantics. That is, two record operands are equal when both of them are null or corresponding values of all fields and automatically implemented properties are equal.
public class RecordTypesEquality
{
public record Point(int X, int Y, string Name);
public record TaggedNumber(int Number, List<string> Tags);
public static void Main()
{
var p1 = new Point(2, 3, "A");
var p2 = new Point(1, 3, "B");
var p3 = new Point(2, 3, "A");
Console.WriteLine(p1 == p2); // output: False
Console.WriteLine(p1 == p3); // output: True
var n1 = new TaggedNumber(2, new List<string>() { "A" });
var n2 = new TaggedNumber(2, new List<string>() { "A" });
Console.WriteLine(n1 == n2); // output: False
}
}
As the preceding example shows, the equality of reference-type members is compared using their specific equality implementations.
String equality
Two string operands are equal when both of them are null or both string instances are of the same length and have identical characters in each character position:
string s1 = "hello!";
string s2 = "HeLLo!";
Console.WriteLine(s1 == s2.ToLower()); // output: True
string s3 = "Hello!";
Console.WriteLine(s1 == s3); // output: False
String equality comparisons are case-sensitive ordinal comparisons. For more information about string comparison, see How to compare strings in C#.
Delegate equality
Two delegate operands of the same run-time type are equal when both of them are null or their invocation lists are the same length and have equal entries in each position:
Action a = () => Console.WriteLine("a");
Action b = a + a;
Action c = a + a;
Console.WriteLine(object.ReferenceEquals(b, c)); // output: False
Console.WriteLine(b == c); // output: True
Important
Equal entries in an invocation list include all fixed parameters in the invocation, including the receiver. The receiver is the instance of an object represented by this when the entry is invoked.
var o1 = new object();
var o2 = new object();
var d1 = o1.ToString;
var d2 = o2.ToString;
Console.WriteLine(object.ReferenceEquals(d1, d2)); // output: False
Console.WriteLine(d1 == d2); // output: False (different receivers)
For more information, see the Delegate equality operators section of the C# language specification.
Delegates that come from evaluating semantically identical lambda expressions aren't equal, as the following example shows:
Action a = () => Console.WriteLine("a");
Action b = () => Console.WriteLine("a");
Console.WriteLine(a == b); // output: False
Console.WriteLine(a + b == a + b); // output: True
Console.WriteLine(b + a == a + b); // output: False
Inequality operator !=
The inequality operator != returns true if its operands aren't equal, and false otherwise. For the operands of the built-in types, the expression x != y produces the same result as the expression !(x == y). For more information about type equality, see the Equality operator section.
The following example demonstrates how to use the != operator:
int a = 1 + 1 + 2 + 3;
int b = 6;
Console.WriteLine(a != b); // output: True
string s1 = "Hello";
string s2 = "Hello";
Console.WriteLine(s1 != s2); // output: False
object o1 = 1;
object o2 = 1;
Console.WriteLine(o1 != o2); // output: True
Equality in class hierarchies
Records handle inheritance correctly without manual work. The compiler-generated equality checks both runtime type and all declared properties, so it automatically satisfies the symmetry and transitivity requirements. Prefer record over a manual unsealed hierarchy when value equality is the goal.
Important
Use record whenever possible — the compiler generates all required equality members for you. Manual implementation is only needed when your type must derive from a non-record class or has other constraints that prevent record.
Implement equality yourself when a type can't be a record
Here is a minimal manual implementation for a value type that can't be a record:
class Color : IEquatable<Color>
{
public Color(int r, int g, int b)
{
R = r;
G = g;
B = b;
}
public int R { get; }
public int G { get; }
public int B { get; }
public bool Equals(Color? other) =>
other is not null && R == other.R && G == other.G && B == other.B;
public override bool Equals(object? obj) => obj is Color other && Equals(other);
public override int GetHashCode() => HashCode.Combine(R, G, B);
}
The implementation provides three required members: Equals(T?) as the core comparison, override Equals(object?) for object-level calls, and override GetHashCode() so hash-based collections work correctly. HashCode.Combine is a library helper that builds one hash from the same values used by Equals. Implementing IEquatable<T> (the Equals(T?) overload) is optional but avoids boxing when callers already have the concrete type.
When you also define == and !=, the language requires them as a pair; warnings CS0660 and CS0661 remind you to keep all four members consistent.
With the three members above in place, Equals reflects value equality, but == still tests identity because no == operator has been declared yet:
var red1 = new Color(255, 0, 0);
var red2 = new Color(255, 0, 0);
Console.WriteLine(red1.Equals(red2)); // => True
Console.WriteLine(red1 == red2); // => False (no == overload; identity check)
A correct implementation must also satisfy the equivalence contract (assume x, y, and z are non-null):
- Reflexive:
x.Equals(x)returnstrue. - Symmetric:
x.Equals(y)returns the same value asy.Equals(x). - Transitive: if
x.Equals(y)andy.Equals(z)are bothtrue, thenx.Equals(z)must betrue. - Consistent: successive calls to
x.Equals(y)return the same value as long as neither object changes. - Null behavior:
x.Equals(null)returnsfalse;x.Equals(y)must not throw when called on a non-nullx.
Value equality in an unsealed class hierarchy requires more care than in a sealed class to satisfy the symmetric and transitive rules. The hazard is that IEquatable<T>.Equals(T? other) dispatch follows the declared type (the type written in the variable declaration) of the variable, not its runtime type. If Shape declares a non-virtual Equals(Shape? other), a variable typed as Shape that holds a Circle at runtime invokes Shape.Equals—silently ignoring Circle-specific fields. Two Circle objects with different radii can compare as equal when accessed through a Shape variable.
The correct pattern requires two cooperating requirements: make the typed Equals method virtual so each derived class can extend the comparison, and add a GetType() == other.GetType() guard in the base-class implementation so objects of different runtime types are never considered equal.
Base class implementation
// Shape is an unsealed base class. Making Equals virtual and guarding with GetType()
// ensures a derived instance is never equal to an instance of a different runtime type.
class Shape : IEquatable<Shape>
{
public string Color { get; }
public Shape(string color) => Color = color;
public override bool Equals(object? obj) => Equals(obj as Shape);
// virtual so derived classes can override and augment the comparison
public virtual bool Equals(Shape? other) =>
other is not null &&
GetType() == other.GetType() && // reject different runtime types
Color == other.Color;
// GetType() is included because equality requires matching runtime types
public override int GetHashCode() => HashCode.Combine(GetType(), Color);
public static bool operator ==(Shape? l, Shape? r) => l?.Equals(r) ?? r is null;
public static bool operator !=(Shape? l, Shape? r) => !(l == r);
}
Key points:
virtualtypedEquals: each derived class overrides this method to augment the comparison with its own fields.GetType()guard:GetType() == other.GetType()prevents aCirclefrom equaling aShapewith the same color, and prevents objects of different derived types from equaling each other.GetHashCodeincludesGetType(): because two objects are equal only when their runtime types match,GetHashCodemust hash the runtime type as well as the data fields. OmittingGetType()here causes incorrect behavior inDictionary<TKey,TValue>andHashSet<T>.==delegates toEquals: keeps operator and method equality consistent.
Derived class implementation
A derived class that adds fields overrides the typed Equals, casts to its own type, calls base.Equals, then compares its own fields:
class Circle : Shape
{
public double Radius { get; }
public Circle(string color, double radius) : base(color) => Radius = radius;
public override bool Equals(object? obj) => Equals(obj as Shape);
// Calls base.Equals to verify Color and runtime type, then adds Radius
public override bool Equals(Shape? other) =>
other is Circle c && base.Equals(c) && Radius == c.Radius;
public override int GetHashCode() => HashCode.Combine(GetType(), Color, Radius);
}
base.Equals(c) enforces the GetType() guard and checks the shared fields. The cast via other is Circle c fails fast when the argument is a Shape of any other derived type.
Usage through a base-type variable
Shape circle1 = new Circle("red", 5.0);
Shape circle2 = new Circle("red", 7.0);
Shape circle3 = new Circle("red", 5.0);
Shape shape1 = new Shape("red");
Console.WriteLine(circle1.Equals(circle2)); // => False (Radius differs)
Console.WriteLine(circle1.Equals(circle3)); // => True
Console.WriteLine(circle1.Equals(shape1)); // => False (different runtime types)
Sealed classes are simpler
You can't subclass a sealed class, so compile-time and runtime types always agree. You don't need the GetType() guard or virtual dispatch. The IEquatable<T> pattern shown in Implement equality yourself when a type can't be a record is correct and complete for a sealed class.
Operator overloadability
You can overload the == and != operators in a user-defined type. If you overload one of these two operators, you must also overload the other operator.
You can't explicitly overload the == and != operators in a record type. To change the behavior of the == and != operators for record type T, implement the IEquatable<T>.Equals method with the following signature:
public virtual bool Equals(T? other);
C# language specification
For more information, see the Relational and type-testing operators section of the C# language specification.
For more information about equality of record types, see the Equality members section of the C# language specification.