Cover | Introduction | Basics | Classes | Advanced Topics | The .NET Framework | Index
C# operators and their precedence closely resemble the operators in other languages of the C family.
Similar to C++, classes can overload most operators, defining or redefining the behavior of the operators in contexts where the first argument of that operator is an instance of that class, but doing so is often discouraged for clarity.
Operators can be grouped by their arity as unary, binary.
Following are the built-in behaviors of C# operators.
The following arithmetic operators operate on numeric operands (arguments and in the "sample usage" below).
|plus||binary||returns the sum of its arguments.|
|minus||binary||returns the difference between its arguments.|
|times||binary||returns the multiplicative product of its arguments.|
|divided by||binary||returns the quotient of its arguments. If both of its operators are integers, it obtains that quotient using integer division (i.e. it drops any resulting remainder).|
|mod||binary||operates only on integer arguments. It returns the remainder of integer division of those arguments. (See modular arithmetic.)|
|plus plus or Postincrement||unary||operates only on arguments that have an l-value. When placed after its argument, it increments that argument by 1 and returns the value of that argument before it was incremented.|
|plus plus or Preincrement||unary||operates only on arguments that have an l-value. When placed before its argument, it increments that argument by 1 and returns the resulting value.|
|minus minus or Postdecrement||unary||operates only on arguments that have an l-value. When placed after its argument, it decrements that argument by 1 and returns the value of that argument before it was decremented.|
|minus minus or Predecrement||unary||operates only on arguments that have an l-value. When placed before its argument, it decrements that argument by 1 and returns the resulting value.|
The following logical operators operate on boolean or integral operands, as noted.
|bitwise and||binary||evaluates both of its operands and returns the logical conjunction ("AND") of their results. If the operands are integral, the logical conjunction is performed bitwise.|
|and||binary||operates on boolean operands only. It evaluates its first operand. If the result is false, it returns false. Otherwise, it evaluates and returns the results of the second operand. Note that, if evaluating the second operand would hypothetically have no side effects, the results are identical to the logical conjunction performed by the operator. This is an example of Short Circuit Evaluation.|
|bitwise or||binary||evaluates both of its operands and returns the logical disjunction ("OR") of their results. If the operands are integral, the logical disjunction is performed bitwise.|
|or||binary||operates on boolean operands only. It evaluates the first operand. If the result is true, it returns true. Otherwise, it evaluates and returns the results of the second operand. Note that, if evaluating the second operand would hypothetically have no side effects, the results are identical to the logical disjunction performed by the operator. This is an example of Short Circuit Evaluation.|
|x-or||binary||returns the exclusive or ("XOR") of their results. If the operands are integral, the exclusive or is performed bitwise.|
|not||unary||operates on a boolean operand only. It evaluates its operand and returns the negation ("NOT") of the result. That is, it returns true if evaluates to false and it returns false if evaluates to true.|
|bitwise not||unary||operates on integral operands only. It evaluates its operand and returns the bitwise negation of the result. That is, returns a value where each bit is the negation of the corresponding bit in the result of evaluating .|
|left shift||binary||evaluates its operands and returns the resulting first argument left-shifted by the number of bits specified by the second argument. It discards high-order bits that shift beyond the size of its first argument and sets new low-order bits to zero.|
|right shift||binary||evaluates its operands and returns the resulting first argument right-shifted by the number of bits specified by the second argument. It discards low-order bits that are shifted beyond the size of its first argument and sets new high-order bits to the sign bit of the first argument, or to zero if the first argument is unsigned.|
The binary relational operators , , , , , and are used for relational operations and for type comparisons.
|is equal to||For arguments of value type, the operator returns true, if its operands have the same value, false otherwise. For the string type, it returns true, if the strings' character sequences match. For other reference types (types derived from ), however, returns true only if and reference the same object.|
|is not equal to||The operator returns the logical negation of the operator . Thus, it returns true, if is not equal to , and false, if they are equal.|
|is less than||The operator operates on integral types. It returns true, if is less than , false otherwise.|
|is greater than||The operator operates on integral types. It returns true, if is greater than , false otherwise.|
|is less than or equal to||The operator operates on integral types. It returns true, if is less than or equal to , false otherwise.|
|is greater than or equal to||The operator operates on integral types. It returns true, if is greater than or equal to , false otherwise.|
The assignment operators are binary. The most basic is the operator . Not surprisingly, it assigns the value (or reference) of its second argument to its first argument.
(More technically, the operator requires for its first (left) argument an expression to which a value can be assigned (an l-value) and for its second (right) argument an expression that can be evaluated (an r-value). That requirement of an assignable expression to its left and a bound expression to its right is the origin of the terms l-value and r-value.)
The first argument of the assignment operator () is typically a variable. When that argument has a value type, the assignment operation changes the argument's underlying value. When the first argument is a reference type, the assignment operation changes the reference, so the first argument typically just refers to a different object, but the object that it originally referenced does not change (except that it may no longer be referenced and may thus be a candidate for garbage collection).
|equals (or set to)||The operator evaluates its second argument and then assigns the results to (the l-value indicated by) its first argument.|
|set to, and then set to||Equivalent to . When there are consecutive assignments, the right-most assignment is evaluated first, proceeding from right to left. In this example, both variables and have the value of .|
The short-hand assignment operators shortens the common assignment operation of into , resulting in less typing and neater syntax.
|plus equals (or increment by)||Equivalent to .|
|minus equals (or decrement by)||Equivalent to .|
|multiply equals (or multiplied by)||Equivalent to .|
|divide equals (or divided by)||Equivalent to .|
|mod equals||Equivalent to .|
|and equals||Equivalent to .|
|or equals||Equivalent to .|
|xor equals||Equivalent to .|
|left-shift equals||Equivalent to .|
|right-shift equals||Equivalent to .|
|returns true, if the variable of base class type stores an object of derived class type T, or, if is of type . Else returns false.|
|returns (x cast to T), if the variable of base class type stores an object of derived class type , or, if is of type . Else returns null. Equivalent to|
|returns the size of the value type . Remarks: The sizeof operator can be applied only to value types, not reference types..|
|returns a object describing the type. must be the name of the type, and not a variable. Use the method to retrieve run-time type information of variables.|
NOTE: Most C# developers agree that direct manipulation and use of pointers is not recommended in C#. The language has many built-in classes to allow you to do almost any operation you want. C# was built with memory-management in mind and the creation and use of pointers is greatly disruptive to this end. This speaks to the declaration of pointers and the use of pointer notation, not arrays. In fact, a program may only be compiled in "unsafe mode", if it uses pointers.
|Indirection operator. Allows access the object being pointed.|
|Similar to the operator. Allows access to members of classes and structs being pointed.|
|Used to index a pointer.|
|References the address of the pointer.|
|allocates memory on the stack.|
|Temporarily fixes a variable in order that its address may be found.|
Overflow exception control
|uses overflow checking on value|
|avoids overflow checking on value|
|accesses member of type or namespace|
|the value of index in|
|casts the value to type|
|creates an object of type|
|if and are strings, concatenates and . If any addend is , the empty string is used instead. If one addend is a string and the other one is a non-string object, is called on that object before concatenation.|
|if and are delegates, performs delegate concatenation|
|if is true, returns the value of , otherwise|
|if is , returns , otherwise returns|
|verbatim text, i.e., escape characters are ignored|
I'd like to elaborate on a specific point Eric Lippert made in his answer and put the spotlight on a particular occasion that hasn't at all been touched upon by anyone else. Eric said:
[...] an assignment almost always leaves behind the value that was just assigned in a register.
I'd like to say that the assignment will always leave behind the value we tried to assign to our left operand. Not just "almost always". But I don't know because I haven't found this issue commented in the documentation. It might theoretically be a very effective implemented procedure to "leave behind" and not reevaluate the left operand, but is it efficient?
'Efficient' yes for all the examples so far constructed in the answers of this thread. But efficient in the case of properties and indexers that use get- and set accessors? Not at all. Consider this code:
Here we have a property, which isn't even a wrapper for a private variable. Whenever called upon he shall return true, whenever one tries to set his value he shall do nothing. Thus whenever this property is evaluated, he shall be truthy. Let's see what happens:
Guess what it prints? It prints . As it turns out, the set accessor is indeed called, which does nothing. But thereafter, the get accessor is never called at all. The assignment simply leaves behind the value we tried to assign to our property. And this value is what the if statement evaluates.
I'll finish off with a real world example that got me researching this issue. I made an indexer which was a convenient wrapper for a collection () that a class of mine had as a private variable.
The parameter sent to the indexer was a string, which was to be treated as a value in my collection. The get accessor would simply return true or false if that value existed in the list or not. Thus the get accessor was another way to use the method.
If the indexer's set accessor was called with a string as an argument and the right operand was a bool , he would add that parameter to the list. But if the same parameter was sent to the accessor and the right operand was a bool , he would instead delete the element from the list. Thus the set accessor was used as a convenient alternative to both and .
I thought I had a neat and compact "API" wrapping the list with my own logic implemented as a gateway. With the help of an indexer alone I could do many things with a few set of keystrokes. For instance, how can I try to add a value to my list and verify that it's in there? I thought this was the only line of code necessary:
But as my earlier example showed, the get accessor which is supposed to see if the value really is in the list wasn't even called. The value was always left behind effectively destroying whatever logic I had implemented in my get accessor.