Operators and expressions

Operators and expressions

Operators

C# provides a large set of operators, which are symbols that specify which operations to perform in an expression. Operations on integral types such as ==, !=, <, >, <=, >=, binary +, binary –, ^, &, |, ~, ++, , and sizeof() are generally allowed on enumerations. In addition, many operators can be overloaded by the user, thus changing their meaning when applied to a user-defined type.

The following table lists the C# operators grouped in order of precedence. Operators within each group have equal precedence.

Operator category Operators
Primary x.yf(x)a[x]x++x–

new

typeof

checked

unchecked

->

Unary +!~++x

–x

(T)x

true

false

&

sizeof

Multiplicative */%
Additive +
Shift <<>>
Relational and type testing <><=>=is

as

Equality ==!=
Logical AND &
Logical XOR ^
Logical OR |
Conditional AND &&
Conditional OR ||
Conditional ?:
Assignment =+=-=*=/=

%=

&=

|=

^=

<<=

>>=

??

The arithmetic operators (+, , *, /) can produce results that are outside the range of possible values for the numeric type involved. You should refer to the section on a particular operator for details, but in general:

  • Integer arithmetic overflow either throws an OverflowException or discards the most significant bits of the result. Integer division by zero always throws a DivideByZeroException.
  • Floating-point arithmetic overflow or division by zero never throws an exception, because floating-point types are based on IEEE 754 and so have provisions for representing infinity and NaN (Not a Number).
  • Decimal arithmetic overflow always throws an OverflowException. Decimal division by zero always throws a DivideByZeroException.

When integer overflow occurs, what happens depends on the execution context, which can be checked or unchecked. In a checked context, an OverflowException is thrown. In an unchecked context, the most significant bits of the result are discarded and execution continues. Thus, C# gives you the choice of handling or ignoring overflow.

In addition to the arithmetic operators, integral-type to integral-type casts can cause overflow, for example, casting a long to an int, and are subject to checked or unchecked execution. However, bitwise operators and shift operators never cause overflow.

In C#, an operator is a term or a symbol that takes one or more expressions, called operands, as input and returns a value. Operators that take one operand, such as the increment operator (++) or new, are called unary operators. Operators that take two operands, such as arithmetic operators (+,,*,/) are called binary operators. One operator, the conditional operator (?:), takes three operands and is the sole tertiary operator in C#.

The following C# statement contains a single unary operator, and a single operand. The increment operator, ++, modifies the value of the operand y.:

y++;

The following C# statement contains two binary operators, each with two operands. The assignment operator, =, has the integer y, and the expression 2 + 3 as operands. The expression 2 + 3 itself contains the addition operator, and uses the integer values 2 and 3 as operands:

y = 2 + 3;

An operand can be a valid expression of any size, composed of any number of other operations.

Operators in an expression are evaluated in a specific order known as operator precedence. The following table divides the operators into categories based on the type of operation they perform. The categories are listed in order of precedence.

Primary x.y, f(x), a[x], x++, x, new, typeof, checked, unchecked
Unary +, , !, ~, ++x, –x, (T)x
Arithmetic — Multiplicative *, /, %
Arithmetic — Additive +,
Shift <<, >>
Relational and type testing <, >, <=, >=, is, as
Equality ==, !=
Logical, in order of precedence &, ^, |
Conditional, in order of precedence &&, ||, ?:
Assignment =, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=

When two operators with the same precedence are present in an expression, they are evaluated based on associativity. Left-associative operators are evaluated in order from left to right. For example, x * y / z is evaluated as (x * y) / z. Right-associative operators are evaluated in order from right to left. The assignment operators and the tertiary operator (?:) are right-associative. All other binary operators are left-associative. However, C# standard does not specify when, in an expression, the “set” portion of an increment instruction is executed. For example, the output of the following example code is 6:

int num1 = 5;
num1++;
System.Console.WriteLine(num1);

However, the output of the following example code is undefined:

int num2 = 5;
num2 = num2++;  //not recommended
System.Console.WriteLine(num2);

Therefore, the latter example is not recommended. Parentheses can be used to surround an expression and force that expression to be evaluated before any others. For example, 2 + 3 * 2 would normally become 8. This is because multiplicative operators take precedence over additive operators. Writing the expression as (2 + 3 ) * 2 results in 10, because it indicates to the C# compiler that the addition operator (+) must be evaluated before the multiplication operator (*).

The following table shows a set of operators used in the C# language.

Category Symbol
Sign operators + -
Arithmetic + - * / %
Logical (boolean and bitwise) & | ^ ! ~ && || true false
String concatenation +
Increment, decrement ++ --
Shift << >>
Relational == != < > <= >=
Assignment = += -= *= /= %= &= |= ^= <<= >>=
Member access .
Indexing []
Cast ()
Ternary ?:
Delegate concatenation and removal + -
Object creation new
Type information as is sizeof typeof
Overflow exception control checked unchecked
Indirection and address * -> [] &
Lambda =>

An operator usually has one or two operands. Those operators that work
with only one operand are called unary operators.
Those who work with two operands are called binary operators.
There is also one ternary operator (?:), which works with three operands.

Certain operators may be used in different contexts. For example the + operator.
From the above table we can see, that it is used in different cases.
It adds numbers, concatenates strings or delegates; indicates the sign
of a number. We say, that the operator is overloaded.

Sign operators

There are two sign operators. + and -. They are used to indicate or
change the sign of a value.

using System;

public class CSharpApp
{
    static void Main()
    {
        Console.WriteLine(2);
        Console.WriteLine(+2);
        Console.WriteLine(-2);
    }
}

+ and – signs indicate the sign of a value. The plus sign can be used
to indicate that we have a positive number. It can be omitted and it
is mostly done so.

using System;

public class CSharpApp
{
    static void Main()
    {
        int a = 1;
        Console.WriteLine(-a);    // prints -1
        Console.WriteLine(-(-a)); // prints 1
    }
}

The minus sign changes the sign of a value.

The assignment operator

The assignment operator = assigns a value to a variable. A variable
is a placeholder for a value. In mathematics, the = operator has a different
meaning. In an equation, the = operator is an equality operator. The left
side of the equation is equal to the right one.

int x = 1;
Console.WriteLine(x); // prints 1

Here we assign a number to the x variable.

x = x + 1;
Console.WriteLine(x);

The previous expression does not make sense in mathematics. But it is legal in programming.
The expression adds 1 to the x variable. The right side is equal to 2 and 2 is assigned to x.

3 = x;

This code example results in syntax error. We cannot assign a value to a literal.

Concatenating strings

In C# the + operator is also used to concatenate strings.

using System;

public class CSharpApp
{
    static void Main()
    {
        Console.WriteLine("Return " + "of " + "the king.");
    }
}

We join three strings together using string concatenation operator.

$ ./catstrings.exe 
Return of the king.

And this is, what we get.

Increment, decrement operators

Incrementing or decrementing a value by one is a common task in
programming. C# has two convenient operators for this. ++ and –.

x++;
x = x + 1;
...
y--;
y = y - 1;

The above two pairs of expressions do the same.

using System;

public class CSharpApp
{
    static void Main()
    {
        int x = 6;

        x++;
        x++;

        Console.WriteLine(x);

        x--;
        Console.WriteLine(x);
    }
}

In the above example, we demonstrate the usage of both
operators.

int x = 6;

x++;
x++;

We initiate the x variable to 6. Then we increment the
x two times. Now the variable equals to 8.

x--;

We use the decrement operator. Now the variable equals to
7.

$ ./incdec.exe 
8
7

And here is the output of the example.

Arithmetic operators

The following is a table of arithmetic operators in C#.

Symbol Name
+ Addition
Subtraction
* Multiplication
/ Division
% Remainder

The following example shows arithmetic operations.

using System;

public class CSharpApp
{
    static void Main()
    {
        int a = 10;
        int b = 11;
        int c = 12;

        int add = a + b + c;
        int sb = c - a;
        int mult = a * b;
        int div = c / 3;
        int rem = c % a;

        Console.WriteLine(add);
        Console.WriteLine(sb);
        Console.WriteLine(mult);
        Console.WriteLine(div);
        Console.WriteLine(rem);
    }
}

In the preceding example, we use addition, subtraction, multiplication,
division and remainder operations. This is all familiar from the mathematics.

int rem = c % a;

The % operator is called the remainder or the modulo operator. It finds the
remainder of division of one number by another. For example, 9 % 4, 9 modulo 4 is 1,
because 4 goes into 9 twice with a remainder of 1.

$ ./arithmetic.exe 
33
2
110
4
2

Output of the example.


Next we will show the distinction between integer and floating
point division.

using System;

public class CSharpApp
{
    static void Main()
    {
        int c = 5 / 2;
        Console.WriteLine(c);

        double d = 5 / 2.0;
        Console.WriteLine(d);
    }
}

In the preceding example, we divide two numbers.

int c = 5 / 2;
Console.WriteLine(c);

In this code, we have done integer division. The returned value
of the division operation is an integer. When we divide two integers
the result is an integer.

double d = 5 / 2.0;
Console.WriteLine(d);

If one of the values is a double or a float, we perform a
floating point division. In our case, the second operand
is a double so the result is a double.

$ ./division.exe 
2
2.5

Result of the division.exe program.

Boolean operators

In C#, we have the following logical operators.
Boolean operators are also called logical.

Symbol Name
&& logical and
|| logical or
! negation
using System;

public class CSharpApp
{
    static void Main()
    {
        int x = 3;
        int y = 8;

        Console.WriteLine(x == y); 
        Console.WriteLine(y > x);

        if (y > x)
        {
            Console.WriteLine("y is greater than x");
        }
    }
}

Many expressions result in a boolean value. Boolean values are used
in conditional statements.

Console.WriteLine(x == y); 
Console.WriteLine(y > x);

Relational operators always result in a boolean value. These two lines
print false and true.

if (y > x)
{
    Console.WriteLine("y is greater than x");
}

The body of the if statement is executed only if the condition
inside the parentheses is met. The y > x returns true, so the message
“y is greater than x” is printed to the terminal.


using System;

public class CSharpApp
{
    static void Main()
    {
        bool a = true && true;
        bool b = true && false;
        bool c = false && true;
        bool d = false && false;

        Console.WriteLine(a);
        Console.WriteLine(b);
        Console.WriteLine(c);
        Console.WriteLine(d);
    }
}

Example shows the logical and operator.
It evaluates to true only if both operands are true.

$ ./andoperator.exe 
True
False
False
False

The logical or || operator evaluates to true,
if either of the operands is true.

using System;

public class CSharpApp
{
    static void Main()
    {
        bool a = true || true;
        bool b = true || false;
        bool c = false || true;
        bool d = false || false;

        Console.WriteLine(a);
        Console.WriteLine(b);
        Console.WriteLine(c);
        Console.WriteLine(d);
    }
}

If one of the sides of the operator is true, the outcome of
the operation is true.

$ ./orop.exe 
True
True
True
False

The negation operator ! makes true false and false true.

using System;

public class CSharpApp
{
    static void Main()
    {
        Console.WriteLine(! true);
        Console.WriteLine(! false);
        Console.WriteLine(! (4 < 3));
    }
}

The example shows the negation operator in action.

$ ./negation.exe 
False
True
True

The ||, and && operators
are short circuit evaluated. Short circuit evaluation means
that the second argument is only evaluated if the first argument does
not suffice to determine the value of the expression: when the first
argument of the logical and evaluates to false, the overall value must
be false; and when the first argument of logical or evaluates to true,
the overall value must be true. (wikipedia)
Short circuit evaluation is used mainly to improve performance.

An example may clarify this a bit more.

using System;

public class CSharpApp
{
    static void Main()
    {
        Console.WriteLine("Short circuit");
        if (One() && Two())
        {
            Console.WriteLine("Pass");
        }

        Console.WriteLine("#############");
        if (Two() || One())
        {
            Console.WriteLine("Pass");
        }
    }

    public static bool One()
    {
        Console.WriteLine("Inside one");
        return false;
    }

    public static bool Two()
    {
        Console.WriteLine("Inside two");
        return true;
    }
}

We have two methods in the example. They are used as operands
in boolean expressions. We will see, if they are called or not.

if (One() && Two())
{
    Console.WriteLine("Pass");
}

The One() method returns false. The short circuit &&
does not evaluate the second method. It is not necessary.
Once an operand is false, the result of the logical conclusion is always
false. Only “Inside one” is only printed to the console.

Console.WriteLine("#############");
if (Two() || One())
{
    Console.WriteLine("Pass");
}

In the second case, we use the || operator and use the Two()
method as the first operand.
In this case, “Inside two” and “Pass” strings are printed to
the terminal. It is again not necessary to evaluate the second
operand, since once the first operand evaluates to true, the
logical or is always true.

$ ./shortcircuit.exe 
Short circuit
Inside one
#############
Inside two
Pass

Result of the shorcircuit.exe program.

Relational Operators

Relational operators are used to compare values. These operators always
result in boolean value. Relational operators are also called comparison
operators.

Symbol Meaning
< less than
<= less than or equal to
> greater than
>= greater than or equal to
== equal to
!= not equal to
Console.WriteLine(3 < 4); ' prints True
Console.WriteLine(3 == 4); ' prints False
Console.WriteLine(4 >= 3); ' prints True
Console.WriteLine(4 != 3); ' prints True

In C# we use the == to compare numbers. Some languages
like Ada, Visual Basic, or Pascal use = for comparing numbers.

Bitwise operators

Decimal numbers are natural to humans. Binary numbers are native to computers.
Binary, octal, decimal or hexadecimal symbols are only notations of the same number.
Bitwise operators work with bits of a binary number. Bitwise operators are seldom
used in higher level languages like C#.

Symbol Meaning
~ bitwise negation
^ bitwise exclusive or
& bitwise and
| bitwise or

The bitwise negation operator changes each 1 to 0 and 0 to 1.

Console.WriteLine(~ 7); // prints -8
Console.WriteLine(~ -8); // prints 7

The operator reverts all bits of a number 7. One of the bits also determines,
whether the number is negative or not. If we negate all the bits one more
time, we get number 7 again.

The bitwise and operator performs bit-by-bit comparison between two numbers.
The result for a bit position is 1 only if both corresponding bits in the operands are 1.

      00110
   &  00011
   =  00010

The first number is a binary notation of 6. The second is 3. The result is 2.

Console.WriteLine(6 & 3); // prints 2
Console.WriteLine(3 & 6); // prints 2

The bitwise or operator performs bit-by-bit comparison between two numbers.
The result for a bit position is 1 if either of the corresponding bits in the operands is 1.

     00110
   | 00011
   = 00111

The result is 00110 or decimal 7.

Console.WriteLine(6 | 3); // prints 7
Console.WriteLine(3 | 6); // prints 7

The bitwise exclusive or operator performs bit-by-bit comparison between two numbers.
The result for a bit position is 1 if one or the other (but not both)
of the corresponding bits in the operands is 1.

      00110
   ^  00011
   =  00101

The result is 00101 or decimal 5.

Console.WriteLine(6 ^ 3); // prints 5
Console.WriteLine(3 ^ 6); // prints 5

Compound assignment operators

The compound assignment operators consist of two operators.
They are shorthand operators.

using System;

public class CSharpApp
{
    static void Main()
    {
        int a = 1;
        a = a + 1;

        Console.WriteLine(a); // prints 2

        a += 5;
        Console.WriteLine(a); // prints 7
    }
}

The += compound operator is one of these shorthand operators.
They are less readable than the full expressions but
experienced programmers often use them.

Other compound operators are:

-=   *=   /=   %=   &=   |=   <<=   >>=

Type information

Now we will concern ourselves with operators that work with
types.

The sizeof operator is used to obtain the size
in bytes for a value type. The typeof is
used to obtain the System.Type object for a type.

using System;

public class CSharpApp
{
    static void Main()
    {   
        Console.WriteLine(sizeof(int));
        Console.WriteLine(sizeof(float));
        Console.WriteLine(sizeof(Int32));

        Console.WriteLine(typeof(int));
        Console.WriteLine(typeof(float));
    }
}

We use the sizeof and typeof operators.

$ gmcs sizetype.cs
$ ./sizetype.exe 
4
4
4
System.Int32
System.Single

Output. We can see, that the int type is an
alias for System.Int32 and the float
is an alias for the System.Single type.


The is operator checks if an object is compatible
with a given type.

using System;

class Base {}
class Derived : Base {}

public class CSharpApp
{
    static void Main()
    {    
        Base _base = new Base();
        Derived derived = new Derived();

        Console.WriteLine(_base is Base);
        Console.WriteLine(_base is Object);
        Console.WriteLine(derived is Base);
        Console.WriteLine(_base is Derived);
    }
}

We create two objects from user defined types.

class Base {}
class Derived : Base {}

We have a Base and a Derived class. The Derived class
inherits from the Base class.

Console.WriteLine(_base is Base);
Console.WriteLine(_base is Object);

Base equals Base and so the first line print True.
A Base is also compatible with Object type. This is
because each class inherits from the mother of all classes,
the Object class.

Console.WriteLine(derived is Base);
Console.WriteLine(_base is Derived);

The derived object is compatible with the
Base class, because it explicitly inherits from
the Base class. On the other hand, the _base
object has nothing to do with the Derived class.

$ ./isoperator.exe 
True
True
True
False

Output of example.


The as operator is used to perform conversions between compatible
reference types. When the conversion is not possible, the operator returns null.
Unlike the cast operation which raises an exception.

using System;

class Base {}
class Derived : Base {}

public class CSharpApp
{
    static void Main()
    {    
        object[] objects = new object[6];
        objects[0] = new Base();
        objects[1] = new Derived();
        objects[2] = "ZetCode";
        objects[3] = 12;
        objects[4] = 1.4;
        objects[5] = null;

        for (int i=0; i<objects.Length; ++i) 
        {
            string s = objects[i] as string;
            Console.Write ("{0}:", i);

            if (s != null)
                Console.WriteLine (s);
            else
                Console.WriteLine ("not a string");
        }
    }
}

In the above example, we use the as operator to
perform casting.

string s = objects[i] as string;

We try to cast various types to the string type. But only
once the casting is valid.

$ ./asoperator.exe 
0:not a string
1:not a string
2:ZetCode
3:not a string
4:not a string
5:not a string

Output.


Expression

An expression is a fragment of code that can be evaluated to a single value, object, method, or namespace. Expressions can contain a literal value, a method invocation, an operator and its operands, or a simple name. Simple names can be the name of a variable, type member, method parameter, namespace or type.

Expressions can use operators that in turn use other expressions as parameters, or method calls whose parameters are in turn other method calls, so expressions can range from simple to very complex.

Literals and Simple Names

The two simplest types of expressions are literals and simple names. A literal is a constant value that has no name. For example, in the following code example, both 5 and “Hello World” are literal values:

int i = 5;
string s = “Hello World”;

In the example above, both i and s are simple names identifying local variables. When those variables are used in an expression, the value of the variable is retrieved and used for the expression. For example, in the following code example, when DoWork is called, the method receives the value 5 by default and is not able to access the variable var:

int var = 5;
DoWork(var);

Invocation Expressions
In the following code example, the call to DoWork is another kind of expression, called an invocation expression.

DoWork(var);

Specifically, calling a method is a method invocation expression. A method invocation requires the name of the method, either as a name as in the previous example, or as the result of another expression, followed by parenthesis and any method parameters. A delegate invocation uses the name of a delegate and method parameters in parenthesis. Method invocations and delegate invocations evaluate to the return value of the method, if the method returns a value. Methods that return void cannot be used in place of a value in an expression.

Variables types, constants and objects
Evaluation order and associativity

Get industry recognized certification – Contact us

keyboard_arrow_up
Open chat
Need help?
Hello 👋
Can we help you?