C# windows forms logical operations. Arithmetic operations

Latest update: 19.06.2017

A separate set of operations represents conditional expressions. Such operations return a boolean value, that is, a value of type bool: true if the expression is true, and false if the expression is false. Similar operations include comparison operations and logical operations.

Comparison Operations

Comparison operations compare two operands and return a bool value - true if the expression is true and false if the expression is false.

    Compares two operands for equality. If they are equal, then the operation returns true; if they are not equal, then false is returned:

    B; // false

    Compares two operands and returns true if the operands are not equal and false if they are equal.

    Int a = 10; int b = 4; bool c = a != b; // true bool d = a!=10; // false

    Operation "less than". Returns true if the first operand is less than the second, and false if the first operand is greater than the second:

    Int a = 10; int b = 4; bool c = a< b; // false

    Operation "more than" Compares two operands and returns true if the first operand is greater than the second, otherwise returns false:

    Int a = 10; int b = 4; bool c = a > b; // true bool d = a > 25; // false

    Less than or equal to operation. Compares two operands and returns true if the first operand is less than or equal to the second. Otherwise it returns false.

    Int a = 10; int b = 4; bool c = a<= b; // false bool d = a <= 25; // true

    Greater than or equal to operation. Compares two operands and returns true if the first operand is greater than or equal to the second, otherwise returns false:

    Int a = 10; int b = 4; bool c = a >= b; // true bool d = a >= 25; // false

Operations<, > <=, >= have higher priority than == and !=.

Logical operations

Also defined in C# logical operators, which also return a value of type bool . They take bool values ​​as operands. Typically applied to relationships and combining multiple comparison operations.

    Logical addition operation or logical OR. Returns true if at least one of the operands returns true.

    Bool x1 = (5 > 6) | (4< 6); // 5 >6 - false, 4< 6 - true, поэтому возвращается true bool x2 = (5 >6) | (4 > 6); // 5 > 6 - false, 4 >

    Logical multiplication or logical AND operation. Returns true if both operands are true at the same time.

    Bool x1 = (5 > 6) & (4< 6); // 5 >6 - false, 4< 6 - true, поэтому возвращается false bool x2 = (5 < 6) & (4 < 6); // 5 < 6 - true, 4 < 6 - true, поэтому возвращается true

    Logical addition operation. Returns true if at least one of the operands returns true.

    Bool x1 = (5 > 6) || (4< 6); // 5 >6 - false, 4< 6 - true, поэтому возвращается true bool x2 = (5 >6) || (4 > 6); // 5 > 6 is false, 4 > 6 is false, so false is returned

    Logical multiplication operation. Returns true if both operands are true at the same time.

    Bool x1 = (5 > 6) && (4< 6); // 5 >6 - false, 4< 6 - true, поэтому возвращается false bool x2 = (5 < 6) && (4 < 6); // 5 < 6 - true, 4 >6 is true, so true is returned

    Logical negation operation. Performed on one operand and returns true if the operand is false. If the operand is true, then the operation returns false:

    Bool a = true; bool b = !a; // false

    Exclusive OR operation. Returns true if either the first or second operand (but not both) is true, otherwise returns false

    Bool x5 = (5 > 6) ^ (4< 6); // 5 >6 - false, 4< 6 - true, поэтому возвращается true bool x6 = (50 > 6) ^ (4 / 2 < 3); // 50 >6 - true, 4/2< 3 - true, поэтому возвращается false

Here we have two pairs of operations | and || (as well as & and &&) perform similar actions, but they are not equivalent.

In the expression z=x|y; Both x and y values ​​will be calculated.

In the expression z=x||y; first, the x value will be calculated, and if it is true, then calculating the y value no longer makes sense, since in any case, z will already be true. The value of y will only be calculated if x is false

The same goes for the &/&& operator pair. The expression z=x will evaluate both x and y.

In the expression z=x&, the value x will first be calculated, and if it is equal to false, then calculating the value of y no longer makes sense, since in our case z will already be equal to false in any case. The value of y will only be calculated if x is true

Therefore the operations || and && are more convenient in calculations, as they reduce the time it takes to calculate the value of an expression, and thereby improve performance. And operations | and & are more suitable for performing bitwise operations on numbers.

The && operator has higher precedence than the || operator. Thus, in the expression true || true && false , the subexpression true && false is evaluated first.

CHAPTER 10. Expressions and Operators

In this chapter, we'll look at the core of any programming language - its ability to perform assignments and comparisons using operators. We'll see what operators are in C# and what their precedence is, and then we'll dive into specific categories of expressions for performing arithmetic operations, assigning values, and comparing operands.

Operators

An operator is a symbol that specifies an operation to be performed on one or more arguments. When the operator is executed, a result is obtained. The syntax for using operators is somewhat different from calling methods, and you should know the format of expressions containing operators in C# like the back of your hand. As in most other languages, the semantics of operators in C# correspond to the rules and notations familiar to us from school. Basic Operators in C# include multiplication (*), division (/), addition and unary plus (+), subtraction and unary minus (-), modulus (%) and assignment (=).

Operators are used to obtain a new value from the values ​​on which the operation is performed. These initial values are called operands. The result of the operation must be stored in memory. Sometimes it is stored in a variable containing one of the original operands. The C# compiler generates an error message when using an operator does not define or store a new value. The code below does not change the values. The compiler will generate an error message because an arithmetic expression that does not change at least one value is usually considered to be in error.

class NoResultApp

{

public static void Main()

{

int i; int j;

i+j; // Error because the result is not assigned to anything. ) >

Most operators only work with numeric types data such as Byte, Short, Long, Integer, Single, Double And Decimal. The exception is the comparison operators (== and !=). Additionally, in C# you can use the + and - operators on a class String and even use the increment operators (++) and (-) for such unusual language constructs as delegates. I will talk about the latter in Chapter 14.

Operator seniority

When there are multiple statements in a single expression, the compiler must determine the order in which they should be executed. In this case, the compiler is guided by rules called seniority of operators. Understanding operator precedence is necessary to correct spelling expressions - sometimes the result may not be as expected.

Consider the expression 42 + 6 * 10. If you add 42 and 6, and then multiply the sum by 10, you get 480. If you multiply 6 by 10 and add 42 to the result, you get 102. When compiling code, a special component of the compiler is lexical analyzer - is responsible for the reading order of this code. It is the lexical analyzer that determines the relative precedence of heterogeneous operators in one expression. To do this, it uses some value - the priority - of each supported operator. Higher priority operators are resolved first. In our example, the * operator has precedence over the + operator, since * absorbs(I'll explain this term now) its operands before + does it. The explanation lies in the general arithmetic rules: multiplication and division Always have more high priority than addition and subtraction. Let's go back to the example: they say that the number is 6 absorbed operator * in both 42 + 6 * 10 and 42 * 6 + 10, so these expressions are equivalent to 42 + (6 * 10) and (42 * 6) + 10.

How precedence is determined in C#

Now let's see how operator precedence is determined in C#. The operators are listed below in descending order of priority (Table 10-1). Next I will tell you more about various categories operators supported in C#.

Table 10-1. Operator precedence in C#.

Operator category Operators
Simple (x), x.y, f(x), a[x], x++, x - , new, typeof, sizeof, checked, unchecked
Unary + , -, !, ++x, - x, (T)x
Multiplicative *,/, %
Additive +, -
Shift «, »
Attitude <, >, <=, >=, is
Equality ==
Logical AND (AND) &
Logical exclusive OR (XOR) ^
Logical OR 1
Conditional AND (AND) &&
Conditional IL AND (OR) II
Condition 9-
Assignment = *= /= % = , + = , -= « = , » = , &=, ^ = , =

Left and right associativity

Associativity determines which part of the expression should be evaluated first. For example, the result of the above expression could be 21 or 33, depending on whether the associativity is used for the “-” operator: left or right.

The - operator has left associativity, i.e. it first evaluates 42-15, and then subtracts 6 from the result. If it had right associativity, it would first evaluate right side expression (15-6), and then the result would be subtracted from 42.

All binary operators (operators with two operands), except assignment operators, are left-associative, that is, they process expressions from left to right. Thus, a + b+ With - same as (a + b) + c, where it is first calculated a + b, and then added to the amount With. Assignment operators and conditional statements - right-associative, that is, they process expressions from right to left. In other words, a=b=c equivalent a = (b= With). Many people stumble on this when they want to put multiple assignment operators on one line, so let's look at this code:

using System;

class RightAssocApp(

public static void Main() (

int a = 1; int b = 2; int c = 3;

Console.WriteLine("a=(0) b=(1) c=(2>", a, b, c); a = b = c;

Console.WriteLine("After "a=b=c - : a=(0) b=(1) c=(2)", a, b, c); > >

The output of this example is:

a=1 b=2 c=3

After "a=b=c": a=3 b=3 o=3

Evaluating expressions from right to left can be confusing at first, but let's approach it this way: if the assignment operator were left-associative, the compiler would first have to evaluate a = b, after which A would be equal to 2 and then b= with and as a result b would be equal to 3. The end result would be a=2 b=3 c=3. Obviously, this is not what we expect when we write A= b= With, and that is why assignment operators and conditional operators are right-associative.

Practical Application

Nothing is as much of a hassle as finding a bug that was made simply because the developer didn't know the rules of precedence and associativity. I came across messages in mail conferences in which seemingly reasonable people proposed a kind of self-documentation mechanism - using spaces to indicate operators that, in their opinion, have seniority. For example, since we know that the multiplication operator takes precedence over the addition operator, we should write code something like this, in which spaces indicate implied seniority:

a = b*c + d;

This approach is fundamentally wrong: the compiler cannot parse code correctly unless a specific syntax is defined. The compiler analyzes the code according to the rules defined by the compiler developers. On the other hand, there are parentheses, used to explicitly indicate precedence and associativity. For example, the expression A= b*c+d can be rewritten as a =(b * c) + d or how A= b*(c+d) and the compiler will evaluate the expression in parentheses first. If there are multiple pairs of parentheses, the compiler will first evaluate the expressions in the parentheses and then the entire expression, based on the precedence and associativity rules described.

I strongly believe that you should always use parentheses when there are multiple operators in an expression. I recommend doing this even if you understand the order of calculations, because the people who will maintain your code may not be so literate.

C# Operators

It is best to consider operators by precedence. Below I will describe the most common operators.

Simple Operators

  • (x) This is a variation of the "bracket" operator to control the order of calculations as in mathematical operations, and when calling methods.
  • x.y The dot operator is used to specify a member of a class or structure. Here X represents an entity containing a member u.
  • f(x) This type of parenthesis operator is used to list method arguments.
  • Oh] Square brackets are used to index an array. These parentheses are also used in conjunction with indexers when objects can be treated as an array. For indexers, see Chapter 7.
  • x++ We will talk about the increment operator separately in the section “Increment and Decrement Operators”.
  • x - We will also consider the decrement operator later.
  • new This operator is used to instantiate objects based on a class definition.

typeof

Reflection reflection is the ability to obtain type information at runtime. This information includes names of types, classes, and structure members. IN. NET Framework this functionality is associated with a class System. Type. This class is the root of all reflection operators and can be obtained using the operator typeof. We won't go into the details of reflection right now (we'll do that in Chapter 16), but here's a simple example to illustrate how easy it is to use the operator typeof"to obtain virtually any information about a type or object during program execution:

using System;

using System.Reflection;

public class Apple (

public int nSeeds;

public void Ripen()

{

> >

public class TypeOfApp (

public static void Main() (

Type t = typeof(Apple);

string className = t.ToStringO;

Console.IgShipe("\nInformation 0 class (About)", className);

Console.WriteLine("\nMeroflH (0)", className); Console. WriteLine("--------"); Methodlnfo methods = t.GetMethodsO;

foreach (MethodInfo method in methods)

Console.WriteLine(method.ToSt ring());

}

Console.WriteLine("\nBce members (O)", className); Console. Writel_ine("--------"); Memberlnfo allMembers = t.GetMembersO; foreach (Memberlnfo member in allMembers)

{

Console. WriteLine(member.ToStringO);

} > }

This program contains a class Apple which has only two members: field nSeeds and method Ripen. First, using the operator typeof and the class name, I get the object System. Type, which is then stored in a variable t. WITH at this point I can use the object System. Type to get all methods and members of a class Apple. This is done using methods GetMethods And GetMembers respectively. The results of these methods are displayed on standard device output as follows:

Information about Apple class Apple Methods

Int32 GetHashCodeQ

System.String ToStringQ

Void RipenO

System.Type GetTypeO

All Apple members

Int32 nSeeds

Int32 GetHashCodeO

Boolean Equals(System.Object)

System.String ToStringO

Void RipenO

System.Type GetTypeO

Before moving on, I want to make two points. First, note that inherited class members are also printed. Since a class is not explicitly derived from another class, we know that all members not defined in the class Apple inherit from an implicit base class System.Object. Secondly, the object System.Type can be obtained using the method GetType. This one is inherited from System.Object The method allows you to work with objects, not classes. Either of the two snippets below can be used to get the object System. Type.

II Retrieving a System.Type object based on a class definition. Type t1 = typeof(Apple);

// Get the System.Type object from the object. Apple apple= new AppleQ; Type t2 = apple.GetTypeO;

sizeof

Operator sizeof used to obtain the size of the specified type in bytes. At the same time, remember only two important factors. Firstly, sizeof can only be applied to dimension types. Therefore, although it can be used for members of classes, it cannot be used for classes themselves. Secondly, sizeof can only be used in methods or code blocks marked as unsafe. WITH We'll look at this kind of code in Chapter 17. Here's an example of using the operator sizeof in a class method marked as unsafe:

using System;

class BasicTypes(

// Note: Code using the sizeof operator should // be marked unsafe, static unsafe public void ShowSizesQ (

Console.WriteLine("\nPa3Mephi basic types"); Console.WriteLine("Pa3Mep short = (0)", sizeof(short)); Console.WriteLine("Pa3Mep int = (0)", sizeof(int)); Console.Writel_ine("Pa3Mep long = (0)", sizeof(long)); Console.WriteLine("Pa3Mep bool = (0)", sizeof(bool)); ) )

class UnsafeUpp

{

unsafe public static void MainQ

{

BasicTypes.ShowSizes();

} }

Here is the result of running this application:

Sizes of main types Size short = 2 int size= 4 Size long = 8 Size bool = 1

Operator sizeof can be used to size not only simple built-in types, but also custom ones dimensional types, such as structures. However, the results sizeof may not be obvious:

// Using the sizeof operator. using System;

struct StructWithNoMembers

struct StructWithMembers

{

short s;

int i;

long 1;

bool b; )

struct CompositeStruct

{

StructWithNoMembers a; StructWithMembers b;

StructWithNoMembers c; )

class UnSafe2App (

unsafe public static void Main() ( Console.WriteLine("\nPa3Mep StructWithNoMembers structure = (0)",

sizeof(StructWithNoMembers)); Console.WriteLine("\nPa3Mep StructWithMembers structure = (0)",

sizeof(StructWithMembers)); Console.WriteLine("\nPa3Mep CompositeStruct structure = (0)",

sizeof(CompositeStruct)); ) )

Although one might expect this application to output 0 for a structure with no members (StructWithNoMembers), 15 for a structure with four members of base types (StructWithMembers) and 15 for a structure that aggregates the previous two (CompositeStruct), in reality the result will be like this:

Size StructWithNoMembers structure = 1 Size StructWithMembers structure = 16

CompositeStruct structure size = 24

The explanation for this is the way the structure is preserved struct by the compiler in the output file, in which the compiler applies alignment and padding. For example, if a structure is 3 bytes in size and is set to be aligned on 4-byte boundaries, the compiler will automatically add 1 byte to the structure, and the statement sizeof will indicate that the size of the structure is 4 bytes. Don't forget to take this into account when sizing structures in C#.

checked And unchecked

These two operators control overflow checking when performing mathematical operations.

Mathematical operators

C#, like most other languages, supports the basic mathematical operators: multiplication (*), division (/), addition (+), subtraction (-), and modulus (%). The purpose of the first four operators is clear from their names; The modulus operator forms the remainder of integer division. Here is code illustrating the use of mathematical operators:

using System;

class MathOpsApp

{

public static void MainQ

{

// The System.Random class is part of the .NET Framework // class library. In its default constructor, // the Next method uses the current date/time as its // initial value. Random random = new RandomO; int a, b, c;

a = rand.Next() % 100; // Limit value 99. b = rand.NextO % 100; // Limit value 99.

Console.WriteLine("a=(0) b=(1)", a, b);

c = a * b;

Console.WriteLineC"a * b = (0)", c);

// Note that we are using integers here. // Therefore, if a is less than b, the result // will always be 0. To obtain a more accurate result // you need to use variables of the double or float type, c = a / b; Console.WriteLineC"a / b = (0)", c);

Console.WriteLineC"a + b = (0)", c);

Console.WriteLineC"a - b = (0)", c);

Console.WriteLineC"a X b = (0)", c); > >

Unary operators

There are two unary operators: plus and minus. The unary minus operator tells the compiler that the number is negative. So in the following code A will be equal to -42:

using System; using System;

class UnarylApp(

public static void Main()

{

int a = 0;

a = -42;

Console.WriteLine("(0)", a); ) )

However, this code introduces ambiguity: using System;

class Unary2App<

public static void Main() (

int a; int b = 2; int c = 42;

a = b * -c;

Console.WriteLine("(0)", a); > >

Expression a= b*-c not entirely clear. Again, using parentheses will make this expression clearer:

// When using parentheses, it's obvious that we're // multiplying b by a negative number c. a = b * (-c);

If a unary minus returns a negative operand, you might think that a unary plus returns a positive. However, the unary plus only returns the operand in its original form and does nothing else, i.e., does not affect the operand. For example, running this code will output the value -84:

using System;

class UnarySApp(

public static void MainQ (

a = b * (+c);

Console.WriteLine("(0)", a); ) )

To obtain a positive value, use the function Math.Abs. This code will output the value 84:

using System;

class Unary4App

{

public static void Main()

int a; int b = 2; int c = -42;

a = b * Math.Abs(c); Console.Writel_ine("(0)", a); ) )

The last unary operator I mentioned is T(x). This is a variation of the "bracket" operator that allows you to cast one type to another. Since it can be overloaded by creating a custom transform, we'll discuss it in Chapter 13.

Compound assignment operators

Compound assignment operator - it's a combination binary operator and the assignment operator (=). The syntax of these operators is:

chorus=y

Where op - this is the operator. Note that in this case the left value (lvalue) not replaced by the right one (rvalue), the compound operator has the same effect as:

x = x op at

And lvalue is used as the basis for the result of the operation.

Notice I said “has the same effect.” The compiler does not translate an expression like x += 5v X= x + 5, he acts logically. Particular attention must be paid to cases where lvalue is a method. Let's look at the code:

using System;

class CompoundAssignmentlApp(

protected elements;

public int GetArrayElementO

{

return elements;

}

CompoundAssignment1App() (

elements = new int;

elements = 42;

}

public static void Main() (

CompoundAssignmentlApp app = new CompoundAssignment1App();

Console.WrlteLine("(0>", app.GetArrayElement());

app.GetArrayElement() = app.GetArrayElement() + 5; Console.WriteLine("(0)", app.GetArrayElement()); ). )

Pay attention to the highlighted line - method call Compound-AssignmentlApp.GetArrayElement and then changing the first element - here I used the syntax:

x = x op at

This is the MSIL code that will be generated: // Inefficient technique: x = x or y.

Method public hldebyslg static void Main() 11 managed

Entrypolnt

// Code size 79 (Ox4f).maxstack 4

Locals (class CompoundAssignmentlApp V_0)

IL_0000: newobj instance void CompoundAssignmentlApp::.ctor() IL_0005: stloc.O IL_0006: Idstr "(OG IL_OOOb: ldloc.0 ILJJOOc: call instance int32

CompoundAssignmentlApp::GetArrayElementO

IL_0011: ldc.14.0

IL_0012: Idelema ["mscorlib"]System.Int32

IL_0017: box [ 1 mscorlib"]System.Int32

IL_001c: call void ["mscorlib 1 ]System.Console::WriteLine (class System.String, class System.Object)

IL_0021: ldloc.0

IL_0022: call instance int32 CompoundAssignmentlApp::GetArrayElementO

IL_0027: Idc.i4.0

IL_0028: ldloc.0

IL_0029: call instance int32 CompoundAssignmentlApp: :GetArrayElementO

IL_002e: Idc.i4.0

IL_002f: ldelem.14

IL_0030: ldc.14.5

IL_0031: add

IL_0032: stealem.14

IL_0033: Idstr "(0)"

IL_0038: ldloc.0

IL_0039: call

instance int32 CompoundAssignmentlApp::GetArrayElement() IL_003e: ldc.14.0

IL_003f: Idelema ["mscorlib"]Systera.Int32 IL_0044: box ["msoorlib"]System.Int32 IL_0049: call void ["mscorlib"]System.Console::WriteLine

(class System.String, class System.Object) IL_004e: ret

) // end of method "CompoundAssignmentlApp::Main" !

Look at the written lines: method CompoundAssignmentlApp.Get-ArrayElement is actually called twice! This is ineffective to say the least, and possibly detrimental depending on what else the method does.

Now let's look at another code that uses compound assignment operator syntax:

using System;

class CompoundAssignment2App (

protected int elements;

public int GetArrayElementO

return elements;

}

CompoundAssignment2App() (

elements = new int;

elements = 42;

}

public static void Main() (

CompoundAssignment2App app = new CompoundAssignment2App();

Console.WriteLine("(0)", app.GetArrayElement());

app.GetArrayElement() += 5; Console.WriteLine("(0)", app.GetArrayElement()); ) )

Usage compound operator assignment will produce much more efficient MSIL code:

// More efficient technique: x op = y.

Method public hidebysig static void Main() il managed

\ {

\.entrypoint

I // Code size 76 (Ox4c) \ .maxstack 4

Locals (class CompoundAssignmentlApp V_0, int32 V_1) \ IL_0000: newobj instance void CompoundAssignmentlApp::.ctor() \ IL_0005: stloc.O 1 IL_0006: Idstr "(0)" 1 IL_OOOb: ldloc.0 IL_OOOc: call instance int32

CompoundAssignmentlApp::GetArrayElementO IL_0011:Idc.i4.0

IL_0012: Idelema [ mscorlib"]System.Int32 IL_0017: box [ > mscorlib - ]System.Int32 lL_001c: call void ["mscorlib"]System.Console::WriteLine

(class System.String, class System.Object) IL_0021: ldloc.0 IL_0022: call instance int32

CompoundAssignmentlApp::GetArrayElement()

IL_0027: dup

IL_0028: stloc.1

IL_0029: Idc.i4.0

IL_002a: ldloc.1

IL_002b: Idc.i4.0

IL_002c:ldelem.14

IL_002d: ldc.14.5

IL_002e: add

IL_002f:stelem.14

IL_0030: Idstr "(0)"

IL_0035: ldloc.0

IL_0036: call instance int32

CompoundAssignmentlApp::GetArrayElementO IL_003b:Idc.i4.0

IL_003c: Idelema ["mscorlib"]System.Int32

IL_0041: box [mscorlib"]System.Int32

IL_0046: call void ["mscorlib"]System.Console::WriteLine

(class System.String, class System.Object)

IL_004b: ret ) // end of the "CompoundAssignmentlApp::Main" method

You can see that the MSIL command was used dup. It duplicates the top element on the stack, thus creating a copy of the value returned by the method CompoundAssignmentlApp.Get Array Element. I

From this it is clear that although essentially x +=y equivalent x = x + y, The MSIL code is different in both cases. This difference should make you think about which syntax to use in each case. The rule of thumb and my recommendation is to use compound assignment operators whenever and wherever possible.

Increment and decrement operators

Introduced in the C language and carried over to C++ and Java, the inc 1 -rement and decrement operators allow you to succinctly express that you want to increase or decrease a numeric value by 1. That is, /++ is equivalent to adding 1 to the current value /".

Having two forms of the increment and decrement operators sometimes leads to confusion. Prefix And postfix The types of these operators differ in the moment at which the value is changed. In the prefix version of the increment and decrement operators (++ai - a respectively) the operation is performed first and then the value is created. In the postfix version (a++ And A-) first the value is created and then the operation is performed. Let's look at an example:

using System;

class IncDecApp (

public static void Foo(int j)

{

Console.WriteLine("IncDecApp.Foo j = (0)", j);

>

public static void Main() (

int i = 1;

Console.WriteLineC"flo calls to Foo(i++) = (0)", i);

Console.WriteLine("After calling Foo(i++) = (0)", i);

Console.WriteLine("\n");

\ Console.WriteLineC"flo calls to Foo(++i) = (0)", i);

\Foo(++l);

\ Console.WrlteLine("After calling Foo(++i) = (0)", i);

l The execution result will be like this:

Before calling Foo(i++) = 1

IncDecApp.Foo j = 1

After calling Foo(i++) = 2

Before calling Foo(-n-i) = 2

IncDecApp.Foo j = 3

After calling Foo(++i) = 3

The difference is When the value is created and the operand is modified. When calling Foo(i++) the value /" is passed (unchanged) to the method Foo And after returns from the / method is incremented by 1. Look at the MSIL code below: command add executed after the value is pushed onto the stack.

IL.0013: ldloc.0

IL.0014: dup

IL_0015: Idc.i4.1

IL_0016: add

IL_0017: stloc.O

IL_0018: call void IncDecApp::Foo(int32)

Now let's look at the prefix form of the operator used in the call Foo(++a). In this case, the MSIL code will be different. At the same time, the team add running to how the value is pushed onto the stack for later method calls Foo.

IL.0049: ldloc.0

IL_004a: Idc.i4.1

IL_004b: add

IL_004c: dup

IL_004d: stloc.O

IL_004e: call void IncDecApp::Foo(int32)

Relational operators

Most operators return numeric values. As for relational operators, they generate a Boolean result. Instead of / instead of performing mathematical operations on a set of operands, / relational operators analyze the relationship between operands and return meaning true, if the relation is true, false - if/ false.

Comparison Operators

Relational operators called comparison operators, relate)-xia “less” (<), «меньше или равно» (<=), «больше» (>), “greater than or equal to” (>=), “equal to” (==) and “not equal to” (!=). The application of these operators to numbers is clear, but when used with objects their implementation is not so obvious. Here's an example:

using System;

class NumericTest(

public NumericTest(int 1)

{

this.i = i;

>

protected int 1; )

class RelationalOpslApp (

public static void Main() (

NumericTest testl = new NumericTest(42); NumericTest test2 = new NumericTest(42);

Console.WriteLine("(0)", testl == test2); > )

If you program in Java, you know what's going to happen here. However, C++ programmers will most likely be surprised to see the result false. Let me remind you: when you create an instance of an object, you get a reference to it. This means that when encountering a relational operator comparing two objects, the C# compiler compares not the contents of the objects, but their addresses. To understand this better, let's look at the MSIL code:

\ .method public hldebysig static void MainQ il managed

\ .entrypoint

\ // Code size 39 (0x27)

1 .maxstack 3

Locals (class NumericTest V_0, \ class NumericTest V_1, 1 bool V_2)

ILJJOOO: Idc.i4.s 42

1L_0002: newobj instance void NumericTest::.ctor(int32)

IL_0007: stloc.O

IL_0008: Idc.i4.s 42

IL_OOOa: newobj instance void NumericTest::.ctor(int32)

IL_OOOf: stloc.1

IL_0010: Idstr "(0)"

IL_0015: ldloc.0

IL_0016: ldloc.1

IL_0017:eeq

IL_0019: stloc.2

IL_001a: Idloca.s V_2

IL_001c: box ["mscorlib"]System.Boolean

IL_0021: call void ["mscorlib"]System.Console::WriteLine

(class System.String,class System.Object)

IL_0026: ret ) // end of the "RelationalOpslApp::Main" method

Look at the line .locals. The compiler indicates that the method Main three local variables. The first two are objects NumericTest and the third is a Boolean variable. Now let's move on to the lines IL_0002 And IL_0007. This is where the object is instantiated testl, and link to it using stloc is stored in the first local variable. It is important that MSIL stores the address of the newly created object. Then in the lines IL_OOOa And IL_OOOf you see MSIL codes to create an object test2 and storing the returned reference in a second local variable. Finally, in the lines 1LJ)015 And IL_0016 local variables are pushed onto the stack by the command Idloc, and in the line IL_0017 team gray hair compares two values ​​at the top of the stack (i.e. object references testl And testl). The return value is stored in a third local variable and then output by the method System.Console. WriteLine.

But how do you compare the members of two objects? The answer is to use an implicit base class for all .NET Framework objects. Precisely, for these purposes, the class System.Object there is a method Equals. For example, the following code compares the contents of objects and outputs, as / would be expected, true: I

using System; /

class RelationalOps2App

( / public static void Main() (

Console.WriteLine("(0)", testl.Equals(test2));

The RelationalOpslApp example uses a "homemade" class (Nu-mericTest), and in the second example - .NET class (Decimal). The point is that the method System.Object.Equals needs to be overridden to do a real member comparison. Therefore, the method Equals with class Nume-ricTest will not work since we did not override the method. And here's the class Decimal overrides the method it inherits equals, and in this case everything will work.

Another way to compare objects is to use operator overloading(operator overloading). Operator overloading defines the operations performed on objects of a particular type. For example, for objects string The + operator does not perform addition, but concatenates strings. We'll look at operator overloading in Chapter 13.

Simple Assignment Operators

The value on the left side of the assignment operator is called lvalue, and on the right side - rvalue. As rvalue can be any constant, variable, number or expression whose result is compatible with lvalue. Meanwhile lvalue must be a variable of a certain type. The point is that the value is copied from the right side to the left. Therefore, physical address space must be allocated for the new value. For example, you can write /" = 4, because / has a place in memory - on the stack or on the heap - depending on the type of the / variable. And here is the operator 4 = 1 cannot be executed because 4 is a value and not a variable whose contents in memory can be changed. By the way, I note that in C# as lvalue may be a variable, property, or indexer. For more information on properties and indexers, see Chapter 7. In this chapter, I use variables for simplicity. If everything is quite clear with the assignment of numeric values, with objects the matter is more complicated. Let me remind you that when you deal with objects, you are not manipulating stack elements, which are easy to copy and move. With objects, you really just have references to some entities for which memory is dynamically allocated. Therefore, when you try to assign an object (or any reference type) to a variable, it is not the data that is copied, as is the case with value types, but the references.

Let's say you have two objects: testl And test2. If you specify testl= test2,testl will not be a copy test2. They will match! Object testl points to the same memory as test2, and any changes to the object testl will lead to changes test2. Here's a program that illustrates this:

using System;

class Foo (

public int i; )

class RefTestlApp (

public static void MainO (

Foo testl = new Foo(); testl.i = 1;

Foo test2 = new Foo(); test2.i = 2;

Console.WriteLine("Before assigning objects"); Console.WriteLine("test1.i=(0>", testl.i); Console.WriteLine("test2.i=(0)", test2.i); Console.WriteLine("\n");

testl = test2;

Console.Writel_ine("After assigning objects");

Console.WriteLine("test1.i=(0)", testl.i); Console.WriteLine("test2.i=(0)", test2.i); Console.WriteLine("\n");

testl.i = 42; ;"

Console.WriteLine("After changing only member TEST1"); Console.WriteLine("test1.i=(0)", testl.i); Console.WriteLine("test2.i=(0)", test2.i); Console.WriteLine("\n"); ) )

When you run this code you will see:

Before assigning an object

test1.i=1

test2.i=2

After assigning an object

testt.i=2

test2.i=2

After changing only the TEST1 member

test1.i=42

test2.i=42

Let's see what happens at each stage of this example. Foo- This is a simple class with a single member, /. The Main method creates two instances of this class: testl And test2- and their members i are set to 1 and 2 respectively. These values ​​are then output and, as expected, testl.i equals 1, a test2.i- 2. And here the fun begins! IN next line object testl assigned test2. Readers who program in Java know what comes next. However, most C++ programmers will expect that the /object member testl is now equal to the object member test2(assuming that when such an application is compiled, some kind of object member copy operator will be executed). The output seems to confirm this. However, in fact, the connection between objects is now much deeper. Let's assign the value 42 to the term testl.i and print the result again. AND?! When an object changes testl changed and testZ This happened because the object testl no more. After assigning it test2 object testl lost because the application no longer references it and as a result it is “cleaned up” by the garbage collector (GC). Now testl And test2 point to the same memory on the heap. Therefore, when changing one variable, the user will see a change in the other.

Notice the last two lines of output: although in the code only the value was changed testl.i, meaning test2.i has also changed. Once again, both variables now point to the same location in memory - this is the behavior that Java programmers expected. However, this does not at all correspond to the expectations of C++ developers, since in this language it is precisely the copying of objects that is carried out: each variable has its own unique copy of the members and changes to one object do not affect the other. Since this is the key to understanding how objects work in C#, let's do small retreat and let's see what happens when passing an object to a method:

using System;

class Foo (

public int i; )

class RefTest2App (

public void ChangeValue(Foo f)

{

f.i = 42;

}

public static void Main() (

RefTest2App app = new RefTest2App();

Foo test = new Foo(); test.i = 6;

Console.WriteLine("Before calling the method"); Console.WriteLine("test.i=(0)", test.i); Console.WriteLine("\n");

app.ChangeValue(test);

Console.WriteLine("After calling the method"); Console.WriteLine("test.i=(0)", test.i); Console.WriteLine("\n"); > )

In most languages ​​other than Java, this code will copy the created object test in local method stack RefTest2App.ChangeValue. In this case, the object test created in method Main, will never see changes to the object/ made in the method ChangeValue. However, I repeat once again that the method Main passes a reference to an object allocated on the heap test. When method ChangeValue manipulates its local variable // it also directly manipulates the object test method Main.

Let's sum it up

The main thing in any programming language is the way it performs assignments, mathematical, logical and relational operations - everything that is required to work real applications. In code, these operations are represented by operators. Factors that influence the execution of statements include the precedence and associativity (right and left) of the statements. C#'s powerful set of predefined operators can be extended with implementations user defined, which we'll talk about in Chapter 13.

In the C# language, there are no implicit conversions to the Boolean type, even for integer arithmetic types. Therefore, a completely correct entry in C++ is:

intk1 = 7;
if (k1) Console. WriteLine(" ok!");

Illegal in C# programs. An error will occur during the translation stage because the evaluated condition is of type int, and the implicit conversion of this type to the type bool absent.

In C#, stricter rules also apply to logical operators. Yes, record if(k1 && (x> y)), correct in C++, leads to an error in

programs in C#, since the && operator is defined only for operands of type bool, and in this expression one of the operands is of type int. In C#, in these situations you should use the following entries:

if(k1>0)
if((k1>0) && (x> y))

Logical operations are divided into two categories: some are performed on the logical values ​​of the operands, while others perform a logical operation on the bits of the operands. For this reason, there are two unary negation operators in C# - logical negation, specified by the "!" operation, and the bitwise negation, specified by the "~" operation. The first of them is defined over an operand of type bool, the second - over the operand of an integer type, starting with the type int and above (int, uint, long, ulong). The result of the operation in the second case is an operand in which each bit is replaced by its complement. Here's an example:

/// < summary>
/// Logical expressions
/// summary>
publicvoidLogic() {
//negation operations ~, !
bool b1, b2;
b1= 2*2 == 4;
b2= !b1;
//b2= ~b1;
uint j1= 7, j2;
j2= ~j1;
//j2= !j1;
int j4= 7, j5;
j5= ~j4;
Console.WriteLine("uint j2= " + j2+ " int j5= " + j5);
} // Logic

This snippet comments out statements that cause errors. In the first case, an attempt was made to apply the bitwise negation operation to an expression like bool, in the second, logical negation was applied to integer data. Both are illegal in C#. Note the different interpretation of bitwise negation for unsigned and signed integer types. For variables j5 And j2 the string of bits that specifies the value is the same, but interpreted differently. The corresponding output is:

uintj2 = 4294967288
intj5 = -8.

Binary logical operations " && - conditional AND" and " || - conditional OR" are defined only over data type bool. The operations are called conditional or shorthand because the evaluation of the second operand depends on the already evaluated value of the first operand. The value of conditional logic operations lies in their efficiency in execution time. Often they allow you to calculate logical expression, which has meaning, but in which the second operand is undefined. Let us take as an example the classic problem of searching by pattern in an array, when an element with a given value (pattern) is searched. There may or may not be such an element in the array. Here is a typical solution to this problem in a simplified form, but conveying the essence of the matter:

// ConditionalAnd- &&
int[] ar= { 1, 2, 3 };
int search= 7;
int i= 0;
while ((i< ar.Length)&& (ar[i]!= search)){
i++;
}
if (i< ar.Length)Console.WriteLine("Samplefound");
elseConsole.WriteLine("SampleNotfound");

If the variable value search(sample) does not match any of the array element values ar, then the last check of the loop condition while will be executed when the value i, equal ar. Length. In this case, the first operand will receive the value false, and, although the second operand is undefined, the loop will terminate normally. The second operand is not defined in last check, because the array element index is out of range (in C#, element indexing starts from zero).

Three binary bitwise operations- "& - AND" , " | -OR", "^-XOR" are used in two ways. They are defined as above integer types above int, and over Boolean types. In the first case they are used as bitwise operations, in the second - as ordinary logical operations. Sometimes it is necessary that both operands be evaluated in any case, then these operations cannot be avoided. Here is an example of their first use:

//Logical bitwise operationsAnd, Or, XOR(&,|,^)
int k2= 7, k3= 5, k4, k5, k6;
k4= k2& k3;
k5= k2 | k3;
k6= k2^k3;
Console.WriteLine("k4= " + k4+ " k5= " + k5+ " k6= " + k6);

Output results:

k4 = 5 k5 = 7 k6 =2

Here is an example of searching by pattern using logical AND: i= 0;

search= ar;
while ((i< ar.Length)& (ar[i]!= search)) i++;
if (i< ar.Length)Console.WriteLine("Samplefound");
else cConsole.WriteLine("SampleNotfound");

This fragment ensures that the search pattern is present in the array, and the fragment will be executed successfully. In the same cases when the array does not contain an element search, an exception will be thrown. The substantive meaning of such a procedure - the occurrence of an exception - may be a sign of an error in the data, which requires special handling of the situation.

Last update: 06/19/2017

C# uses most of the operations that are used in other programming languages. Operations represent certain actions over the operands - participants in the operation. The operand can be a variable or some value (for example, a number). Operations can be unary (performed on one operand), binary - on two operands, and ternary - performed on three operands. Let's consider all types of operations.

Binary arithmetic operations:

    The operation of adding two numbers:

    Int x = 10; int z = x + 12; // 22

    The operation of subtracting two numbers:

    Int x = 10; int z = x - 6; // 4

    The operation of multiplying two numbers:

    Int x = 10; int z = x * 5; // 50

    operation of dividing two numbers:

    Int x = 10; int z = x / 5; // 2 double a = 10; double b = 3; double c = a / b; // 3.33333333

    When dividing, it is worth considering that if both operands represent integers, then the result will also be rounded to an integer:

    Double z = 10 / 4; //result is 2

    Even though the result of the operation is ultimately placed in type variable double, which allows you to save fractional part, but the operation itself involves two literals, which by default are treated as int objects, that is, integers, and the result will also be an integer.

    To get out of this situation, it is necessary to define the literals or variables involved in the operation exactly as double types or float:

    Double z = 10.0 / 4.0; //result is 2.5

    The operation of obtaining the remainder of an integer division of two numbers:

    Double x = 10.0; double z = x % 4.0; //result is 2

There are also a number of unary operations in which one operand takes part:

    Increment operation

    The increment can be prefixed: ++x - first the value of the variable x is increased by 1, and then its value is returned as the result of the operation.

    And also exists postfix increment: x++ - first the value of the variable x is returned as the result of the operation, and then 1 is added to it.

int x1 = 5; int z1 = ++x1; // z1=6; x1=6 Console.WriteLine($"(x1) - (z1)"); int x2 = 5; int z2 = x2++; // z2=5; x2=6 Console.WriteLine($"(x2) - (z2)");

The operation of decrementing or decreasing a value by one. There is also a prefix form of decrement (--x) and a postfix form (x--).

Int x1 = 5; int z1 = --x1; // z1=4; x1=4 Console.WriteLine($"(x1) - (z1)"); int x2 = 5; int z2 = x2--; // z2=5; x2=4 Console.WriteLine($"(x2) - (z2)");

When performing several arithmetic operations at once, the order in which they are performed must be taken into account. Operation priority from highest to lowest:

    Increment, decrement

    Multiplication, division, remainder

    Addition, subtraction

To change the order of operations, parentheses are used.

Consider a set of operations:

Int a = 3; int b = 5; int c = 40; int d = c---b*a; // a=3 b=5 c=39 d=25 Console.WriteLine($"a=(a) b=(b) c=(c) d=(d)");

Here we are dealing with three operations: decrement, subtraction and multiplication. First, the variable c is decremented, then b*a is multiplied, and finally subtracted. That is, in fact, the set of operations looked like this:

Int d = (c--)-(b*a);

But with the help of parentheses we could change the order of operations, for example like this:

Int a = 3; int b = 5; int c = 40; int d = (c-(--b))*a; // a=3 b=4 c=40 d=108 Console.WriteLine($"a=(a) b=(b) c=(c) d=(d)");

Operator associativity

As noted above, multiplication and division operations have the same priority, but what then will be the result in the expression:

Int x = 10 / 5 * 2;

Should we interpret this expression as (10 / 5) * 2 or as 10 / (5 * 2)? After all, depending on the interpretation, we will get different results.

When operations have the same priority, the order of evaluation is determined by the associativity of the operators. Depending on the associativity, there are two types of operators:

    Left-associative operators, which are executed from left to right

    Right-associative operators, which are executed from right to left

All arithmetic operators(except for prefix increment and decrement) are left-associative, that is, executed from left to right. Therefore, the expression 10 / 5 * 2 must be interpreted as (10 / 5) * 2, that is, the result will be 4.