C#  Language Basics

In this chapter , we introduce the basics of the C# language .

- All programs and code snippets in this and the following two chapters are available interactive samples in LINQPad .

Working through these samples in conjunction with this book accelerates learning in that you can edit the samples and instantly see the results without needing to set up projects and solutions in Visual Studio .

To download the samples , click the Sample tab in LINQPad , and then click " Download more samples" LINQPad is free -- > http://www.linqpad.net


A First C# Program

Here is a program that multiplies 12 by 30 and prints the result , 360 , to the screen .

To double forward slash indicates that the remainder of a line is a comment .

using System;                       // Importing namespace

class Test                          // Class declaration

{

    static void Main()                // Method declaration

    {

         int x = 12*30;                 // Statement 1

     Console.WriteLine (x);         // Statement 2

    }                               // End of method

}                                   // End of class

At the heart of this program lie two statements:

int x = 12*30;                 // Statement 1

Console.WriteLine (x);         // Statement 2

Statements in C# execute sequentially and are terminated by a semicolon ( or a code block , as we will see later ) .

The first statement computes the expression 12*30 and stores the result in a local variable , named x , which is an integer type .

The second statement calls the Console class's WriteLine method , to print the variable x to a text window on the screen .

A  method performs an action in a series of statements , called a statement block -- a pair of braces containing zero or more statements .

We defined a single method named Main:

using System;               

class Test                         

{

    static void Main()            

    {  

         Console.WriteLine (FeetToInches(30)); //360

         Console.WrtieLine(FeetToInches(100)); //1200

    }                             

 

    static int FeetToInches(int feet)

    {

    int inches = feet*12;

    return inches;

    }

}                                  

A method can receive input data from the caller by specifying parameters and output data back to the caller by specifying a return type .

We defined a method called FeetToInches that has a parameter for inputting feet , and a return type for outputting inches:

static int FeetToInches(int feet){...}

The literals 30 and 120 are the arguments passed to the FeetToInches method .

The Main method in our example has empty parentheses because it has no parameters , and is void because it doesn't return any value to its caller:

static void Main() 

C# recognizes a method called Main as signaling the default entry point of execution .

The Main method may optionally return an integer ( rather than void ) in order to return a value to the execution environment ( where a nonzero value typically indicates an error ).

The Main method can also optionally accept an array of strings as parameter ( that will be populated with any arguments passes to the executable ) .

For example:

static int Main(string[] args){...}  

- An array ( such as string[] ) represents a fixed number of elements of particular type .

Arrays are specified by placing square brackets after the element type .

Methods are one of several kinds of functions in C# .

Another kind of function we used in our example program was the * operator , which performs multiplication .

There are also constructors , properties , events , indexers , and finalizers .

In our example , the two methods are grouped into a class .

A class groups function members and data members to form an object-oriented building block .

The Console class groups members that handle command-line input/output functionality , such as the WrtieLine method .

Our Test class groups two methods -- the Main method and the FeetToInches method .

A class is a kind of type , which we will examine in "Type Basics".

At the outermost level of a program , types are organized into namespaces .

The using directive was used to make the System namespace available to our application , to use the Console class .

We could define all our class within the TestPrograms namespace , as follows:

using System;

namespace TestPrograms

{

class Test1{...}

class Test2{...}

}

The .NET Framework is organized into nested namespaces .

For example , this is the namespace that contains types for handling text:

using System.Text;

The using directive is there for convenience; you can also refer to a type by its fully qualified name , which is the type name prefixed with its namespace , such as System.Text.StringBuilder .

 

Compilation

The C# compiler compiles source code , specified as a set of files with the .cs extension into an assembly .

An assembly can be either an application or a library .

A normal console or Windows application has a Main method and is an .exe file .

A library is a .dll and is equivalent to an .exe without an entry point .

Its purpose is to be called upon ( referenced ) by an application or by other libraries .

The .NET Framework is a set of libraries .

The name of the C# compiler is csc.exe .

You can either use an IDE such as Visual Studio to compiler , or call csc manually from the command line . ( The compiler is also available as library; see Chapter27 ) .

To compile manually , first save a program to a file such as MyFirstProgram.cs , and then go to the command line and invoke csc ( located in %ProgramFiles(X86)%\msbuild\14.0\bin ) as follows:

csc MyFirstProgram.cs

This produces an application named MyFirstProgram.exe .

- Peculiarly , .NET Framework 4.6 and 4.7 still ship with the C# 5 compiler .

To obtain the C# 7 command-line compiler , you must install Vistual Studio 2017 or MSBuild 15 .

To produce a library ( .dll ) , do the following:

csc /target:library MyFirstProgram.cs

We explain assemblies in detail in Chapter 18 .

 

Syntax

C# syntax is inspired by C and C++ syntax .

In this section , we will describe C#'s elements of syntax , using the following program:

using System.Text;

class Test                         

{

    static int Main()        

    {

         int x = 12*30;

         Console.WriteLine(x);

    }                             

 

Identifiers and Keywords

Identifiers are names that programmers chooes for their classes , methods , variables , and so on .

These are the identifiers in our example program , in the order they appear:

System    Test     Main     x     Console     WriteLine

An identifier must be whole word , essentially made up of Unicode characters starting with a letter or underscore .

C# identifiers are case-sensitive .

By convention , parameters , local variables , and private fields should be in camel case ( e.g. , myVariable ) , and all other identifiers should be in Pascal case ( e.g. , MyMethod ) .

Keywords are names that mean something special to the compiler .

These are the keywords in our example program:

using     class     static     void     int

Most keywords are reserved , which means that you can't use them as identifiers .

Here is the full list of C# reserved keywords:

A-I.jpg

L-S.jpg

T-W.jpg

 

 

Avoiding conflicts

If you really want to use an identifier that clashes with a reserved keyword , you can do so by qualifying it with the @ prefix .

For instance:

class class{...} //Illegal

class @class{...} //Legal

The @ symbol doesn't form part of the identifier itself .

  • So @myVariable is the same as myVariable .

- The @ prefix can be useful when consuming libraries written in other .NET languages that have different keywords .

 

Contextual keywords

Some keywords are contextual , meaning they can also be used as identifier without an @ symbol .

These are:

add    ascending    async    await

by

descending    dynamic

equals

from

get    global    group

in    into

join

let

nameof

on    orderby

partial

remove

select    set

value    var

when    where

yield

With contextual keywords , ambiguity cannot arise within the context in which they are used .

 

Literals , Punctuators , and Operators

Literals are primitive pieces of data lexically embedded into the program .

The literals we used in our example program are 12 and 30 .

Punctuators help demarcate the structure of the program .

These are the punctuators we used in our example program:

{    }    ;

The braces group multiple statements into a statement block .

The semicolon terminates a statement . ( Statement blocks , however , do not require a semicolon ) .

Statements can wrap multiple lines:

Console.WriteLine(1+2+3+4+5+6+7+8+9+10);

An operator transforms and combines expressions .

Most operators in C# are denoted with a symbol , such as the multiplication operator , * .

We will discuss operators in more detail later in this chapter .

These are the operators we used in our example program:

    .    ()    *    =

A period denotes a member of something ( or a decimal point with numeric literals ) .

Parentheses are used when declaring or calling a method; empty parentheses are used when the method accepts no arguments .

( Parentheses also have other purposes that we will see later in this chapter ) .

An equals sign performs assignment . ( The double equals sign , == , performs equality compairson , as we will see later ) .

 

Comments

C# offers two different styles fo source-code documentation: single-line comments and multiline comments ,

A single-line comment begins with a double forward slash and continues until the end of the line .

For example:

int x=3; // Comment about assigning 3 to x

A multiline comment begins with /* and with */  .

For example:

int x=3/* This is a comment that

                    span

                       three lines  */

Comments may embed XML documentation tags .

 

Type Bsics

A type defines the blueprint for a value .

In our example , we used two literal of type int with value 12 and 30 .

We also declared a variable of type int whose name was x:

   

static int Main()        

    {

         int x = 12*30;

         Console.WriteLine(x);

    }                             

}

A variable in C# a storage location that can contain different values over time .

In contrast , a constant always represents the same value ( more on this later ):

const int y=360;

All values in C# are instances of a type .

The meaning of a value , and the set of possible values a  variable can have , is determined by its type .

 

Predefined Type Example

Predefined types are types that are specially supported by the compiler .

The int type is a predefined type for representing the set of integers that fit into 32 bits of memory , from -2^31 to 2^31-1 , and is the default type for numeric literals within this range .

We can perform functions such as arithmetic with instances of the int type as follows:

int x=12*30;

Another predefined C# type is string .

The string type represents a sequence of characters , such as ".NET" or "hello.world" .

We can work with strings by calling functions on them as follows:

 

static void Main()        

    {

         string message="Hello world";

         string upperMessage=message.ToUpper();

         Console.WriteLine(upperMessage);        //HELLO WORLD

        

         int x=2019;

         message=message+x.ToString();

         Console.WriteLine(message);                 //Hello world2019

    }      

                       

The predefined bool type has exactly two possible values: true and false .

The bool type is commonly used to conditionally branch execution flow based with an if statement .

For example:

 

    static void Main()        

    {

         bool simpleVar=false;

        

         if(simpleVar){

         Console.WriteLine("this will not print");

         }

         int x=5000;

         bool lessThanAMile=x<5280;

      

         if(lessThanAMile){

         Console.WriteLine("this will print");

         }

    }    

- In C# , predefined typrs ( also refered to as built-in types ) are recognized with a C# keyword .

The system namespace in the .NET Framework contains many important types that are not predefineded by C# ( e.g. , DateTime ) .

 

Custom Type Example

Just as we can build complex functions from simple functions , we can build complex types from primitive types .

In this example , we will define a custom type named UnitConverter -- a class that serves as a blueprint for unit conversions:

 

public class UnitConverter{

    int ratio;//Field

    public UnitConverter(int unitRatio){ratio=unitRatio;}//Constructor  

    public int Convert(int unit){return unit*ratio;}//Method

}

 

class Test                         

{

    static void Main()        

    {

         UnitConverter feetToInchesConverter = new UnitConverter(12);

         UnitConverter milesToFeetConverter  = new UnitConverter(5280);

        

         Console.WriteLine(feetToInchesConverter.Convert(30));//360

         Console.WriteLine(feetToInchesConverter.Convert(100));//1200

    Console.WriteLine(feetToInchesConverter.Convert(milesToFeetConverter.Convert(1)));//63360

         }

    }      

                       

Members of a type

A type contains data members and function members .

The data member of UnitConverter is the field called ratio .

The function members of UnitConverter are the Convert method and the UnitConverter's constructor .

 

Symmetry of predefined types and custom types

A beautiful aspect of C# is that predefined types and custom types have few defferences .

The predefined int type serves as a blueprint for integers .

It holds data -- 32 bits -- and provides function members that use that data , such as ToString .

Similarly , our custom UnitConverter type acts as a blueprint for unit convensions .

It holds data -- the radio -- and provides function members to use that data .

 

Constructors and instantiation

Data is created by instantiating a type .

Predefined types can be instantiated simply by using a literal such as 12 or "hello world" .

The new operator creates instances of a custom type .

We created and declared an instance of the UnitConverter type with this statement:

UnitConverter feetToInchesConverter = new UnitConverter(12);

Immediately after the new operator instantiates an object , the object's constructor is called to perform initialization .

A consturctor is defined like a method , expect that the method name and return type are reduced to the name of the enclosing type:

public class UnitConverter

{

    ...

    public    UnitConverter( int unitRatio ) {  ratio  =  unitRatio;  }

    ...

}

 

Instance versus static members

The data members and function members that operate on the instance of the type are called instance members .

The UnitConverter's Convert method and the int's ToString method are examples of instance members .

By default , members are instance members .

Data members and function members that don't operate on the instance of the type , but rather on the type itself , must be marked as static .

The Test.Main and Console.Write methods are static methods .

The console class is actually a static alss , which means all its members are static .

You never actually create instances of a Console -- one console is shared across the whole application .

Let's contrast instance from static members .

In the following code , the instance field Name pertains to an instance of a particular Panda , whereas Population pertains to the set of all Panda instances:

 

public class Panda

{

    public string Name;//Instance field

    public static int Population;//Static field

 

    public Panda(string n)//Constructor

    {

         Name = n;//Assign the instance field

         Population = Population+1;//Increment the static Population field

    }

}

 

The following code creates two instances of the Panda , prints their names , and then prints the total population:

 

public class Panda

{

    public string Name;//Instance field

    public static int Population;//Static field

 

    public Panda(string n)//Constructor

    {

         Name = n;//Assign the instance field

         Population = Population+1;//Increment the static Population field

    }

}

 

class Test                        

{

    static void Main()        

    {

         Panda p1 = new Panda("Pan p1");

         Panda p2 = new Panda("Pan p2");

        

         Console.WriteLine(p1.Name);//Pan p1

         Console.WriteLine(p2.Name);//Pan p2

        

         Console.WriteLine(Panda.Population);//

         }

    }                             

Attempting to evaluate p1.Population or Panda.Name will generate a compile-time error .

 

The public keywords

The public keyword exposes members to other classes .

In this example , if the Name field in Panda was not marked as public , it would be private and the Test class could not access it .

Marking a member public is how a type communicates: "Here is what I want other types to see -- everything else is my own private implementation details" .

In object-oriented terms , we say that the public members encapsulate the private members of the class .

 

Conversions

C# can convert between instances of compatible types .

A conversion always creates a new value from an existing one .

Conversions can be either implicit or explicit: implicit conversions happen automatically , and explicit conversions require a cast .

In the following example , we implicitly convert an int to a long type ( which has twice the bitwise capacity of an int ) and explicitly cast an int to a short type ( which has half the capacity of an int ):

int x =12345;     //int is a 32-bit integer

long y = x;    //Implicit conversion to 64-bit integer

short z = (short)x;    //Explicit conversion to 16-bit integer

Implicit conversions are allowed when both of the following are true:

  • The compiler can guarantee they will always succeed .
  • No information is lost in conversion .

Conversely , explicit conversions are required when one of the following is true:

  • The compiler cannot guarantee they will always succeed .
  • Information may be lost during conversion .

( If the compiler can determine that a conversion will always fail , both kinds of conversion are prohibited .

Conversions that involve generics can also fail in certain conditions -- see "Type Parameters and Conversions" in Chapter 3 ) .

-The numeric conversions that we just saw are bult into the language .

C# also supports reference conversions and boxing conversions ( see Chapter 3) as well as custom conversions ( see "Operator Overloading" in Chapter 4) .

The compiler doesn't enforce the aforementioned rules with custom conversions , so it's possible for badly designed types to behave otherwise .

 

Value Types Versus Reference Types

All C# types fall into the following categories:

  • Value types
  • Reference types
  • Generic types
  • Generic type parameters
  • Pointer types

- In this section , we will describe value types and reference types .

We will cover generic type parameter in "Generics" in Chapter 3 , and pointer types in "Unsafe Code and Pointers" in Chapter 4 .

Value types comprise most built-in types ( specifically , all numeric types , the char type , and the bool type ) as well as custom struct and enum types .

Reference types comprise all class , array , delegate , and interface types . ( This includes the predefines string type ) .

The fundamental difference between value types and reference types is how they are handled in memory .

 

Value types

The content of a value type or constant is simply a value .

For example , the content of the built-in value type , int , is 32 bits of data .

You can define a custom value type with the struct keyword:

public struct Point{  public  int  x;    public  int y;  }

or more tersely:

public  struct  Point{  public  int  x,y;  }

The assignment of a value-type instance always copies the instance .

For example:

 

public struct Point

{

public int x,y;

}

class Test                         

{

    static void Main()            

    {  

         Point p1 = new Point();

 

         p1.x = 7;

 

         Point p2 = p1; //Assignment causes copy

 

         Console.WriteLine(p1.x);//7

         Console.WriteLine(p2.x);//7

 

         p1.x = 9; // Change p1.x

 

         Console.WriteLine(p1.x);//9

         Console.WriteLine(p2.x);//7

    }                                 

}

 

shows that p1 and p2 have independent storage .

 

Reference types

A reference type is more complex than a value type , having two parts: an object and the reference to that object .

The content of a reference-type variable or constant  is a reference to an object that contains the value .

Here is the Point type from our previous example rewritten as a class , rather than a struct:

public  class  Point{  public  int  x,y;  }

Assigning a reference-type variable copies the reference , not the object instance .

This allow multiple variables to refer to the same object -- something not ordinarily possible with value types .

If we repeat the previous example , but with Point now a class , an operation to p1 affects p2:

 

public class Point

{

 

public int x,y;

 

}

 

class Test{

 

static void Main()

{

         Point p1 = new Point();

         p1.x = 7;

        

         Point p2 = p1; //Copies p1 reference

        

        

         Console.WriteLine(p1.x);//7

         Console.WriteLine(p2.x);//7

        

         p1.x = 9; // Change p1.x

        

         Console.WriteLine(p1.x);//9

         Console.WriteLine(p2.x);//9

}

()

 

}

 

Null

A reference can be assigned the literal null , indicating that the  reference points to no object:

class Point{  ...  }
...

Point  p  =  null;

Console.WriteLine( p==null);  //true

//The following line generates a runtime error
//( a NullReferenceException is thrown ):
Console.WriteLine(p.x);

In contrast , a value type cannot ordinarily have a null value:

struct  Point{ ... }
...
Point p = null; //Compile-time error
int x = null;      //Compile-time error

- C# also has a construct called nullable types for representing value-type nulls ( see "Nullable Types" in Chapter 4 ).

 

Storage overhead

Value-type instances occupy precisely the memory required to store their fields .

In this example , Point takes eight bytes of memory:

struct  Point

int x;// 4 bytes
int y;// 4 bytes
}

- Technically , the CLR positions fields within the type at an address that's a multiple of the field's size ( up to a maximum of eight bytes ) .

Thus , the following actually consumes 16 bytes of memory ( within the seven bytes following the first field "wasted" ) .

struct A{  byte b;  long  l;  }

You can override this behaviour with the structLayout attribute ( see "Mapping a Struct to Unmanaged Memory" on page.975 ) .

Reference types require separate allocations of memory for the reference and object .

The object consumes as many bytes as its fields , plus additional administrative overhead .

The precise overhead is intrinsically private to the implementation of the .NET runtime , but at minimum , the overhead is eight bytes , used to store a key to the object's type , as well as temporary information such as its lock state for multithreading and a flag to indicate whether it has been fixed from movement by the garbage collector .

Each reference to an object requires an extra four or eight bytes ,depending on whether the .NET runtime is running on a 32- or 64-bit platform .

 

Predefined Type Taxonomy

The predefined types in C# are:

# Value types

  • Numeric

    - Signed integer ( sbyte , short , int , long )

    - Unsigned integer ( byte , ushort , uint , ulong )

    - Real number ( float , double , decimal )

  • Logical ( bool )
  • Character ( char )

# Reference types

  • String ( string )
  • Object ( object )

 

Predefined types in C# alias Framework types in the System namespace .

There is only a syntactic difference between these two statement:

int  i  =  5;

System.Int32  i  =  5;

The set of predefined value types excluding decimal are known as primitive types in the CLR .

Primitive types are so called because they are supported directly via instructions in compiled code , and this usually translates to direct support on the underlying processor .

For example:

// Underlying hexadecimal representation
int i = 7; // 0x7
bool = true; // 0x1
char = 'A'; //0x41
float f = 0.5f; // uses IEEE floating-point encoding

The System.IntPtr and System.UInPtr types are also primitive ( see Chapter 25 ) .

 

Numeric Types

C# has the predefined numeric types in C#

1.jpg

Of the integral types , int and long are first-class citizens and are favored by both C# and the runtime .

The other integral types are typically used for interoperability or when space efficiency is paramount .

Of the real number types , float and double are called floating-point types and are typically used for scientific and graphical calculations .

The decimal type is typically used for financial calculations , where base-10-accurate arithmetic and high precision are required .

 

Numeric Literals

Integral-type literals can use decimal or hexadecimal notation; hexadecimal is denoted with the prefix .

For example:

int x = 127;
long y = 0x7f;

From C#7, you can insert an underscore anywhere inside a numeric literal to make it more readable:

int million = 1_000_000;

C#7 also lets you specify numbers in binary with the "0b" prefix:

var b = 0b1010_1011_1100_1101_1110_1111;

Real literals can use decimal and/or exponential notation .

For example:

double d = 1.5;
double million = 1E06;

 

Numeric literal type inference

By default , the compiler infers a numeric literal to be either double or an integral type:

  • If the literal contains a decimal point or the exponential symbol (E) , it is a double .
  • Otherwise , the literal's type is the first type in the list that can fit the literal's value: int , uint , long , and ulong .

For example:

Console.WriteLine(                1.0.GetType()); // Double (double)
Console.WriteLine(             1E06.GetType()); // Double (double)
Console.WriteLine(                   1.GetType()); // Int32    (int)
Console.WriteLine(  0xF0000000.GetType()); // Uint32 (uint)
Console.WriteLine(0x100000000.GetType()); // Int64   (long)

 

Numeric suffixes

Numeric suffixes explicitly define the type of a literal .

Suffixes can be either lower or uppercase , and are as follows:

2.jpg

The suffixes U and L are rarely necessary , because the uint , long , and ulong types can nearly always be either inferred or implicitly converted from int:

long i = 5;  // Implicit lossless conversion from int literal to long

The D suffix is technically redundant , in that all literals with a decimal point are inferred to be double .

And you can always add a decimal point to a numeric literal:

double x = 4.0;

The F and M suffixes are the most useful and should always be applied when specifying float or decimal literals .

Without the F suffix , the following line would not compile , because 4.5 would be inferred to be of type double , which has no implicit conversion to float:

float  f  =  4.5F;

The same principle is true for a decimal literal:

decimal  d  =  -1.23M;  // Will not compile without the M suffix .

We describe the semantics of numeric conversions in detail in the following section .

 

 

Numeric Conversions

 

Converting between integral types

Integral type conversions are implicit when the destination type can represent every possible value of the source type .

Otherwise , an explicit conversion is required .

For example:

int x = 12345;          // int is a 32-bit integer
long y = x;               // Implicit conversion to 64-bit integral type
short z = (short)x;   // Explicit conversion to 16-bit integral type

 

Converting between floating-point types

A float can be implicitly converted to a double , since a double can represent every possible value of a float .

The reverse conversion must be explicit .

 

Converting between floating-point and integral types

All integral types may be implicitly converted to all floating-point types:

int i = 1;
float f = i;

The reverse conversion must be explicit:

int  i2  =  (int)f;

- When you cast from a floating-point number to an integral type , any fractional portion is truncated; no rounding is performed .

The static class System.Convert provides methods that round while converting between various numeric types ( see Chapter 6 ) .

Implicitly converting a large integral type to a floating-point type preserves magnitube but may occasionally lose precision .

This is because floating-point types always have more magnitude than integral types , but may have less precision .

Rewriting our example with a larger number demonstrates this:

int i1 = 100000001;
float f = i1; // Mangnitude preserved , precision lost
int i2 = (int)f; // 100000000

 

Decimal conversions

All integral types can be implicitly converted to the decimal type , since a decimal can represent every possible C# integral-type value .

All other numeric conversions to and from a decimal type must be explicit .

 

Aritjmetic Operators

The arithmetic operators ( + , - , * , / , % ) are defined for all numeric types except the 8- and 16-bit integral types:

+  Addition

-  Subtraction

*  Multiplication

/  Division

%  Remainder after division

 

Increment and Decrement Operatorswwwwwwwwwwwwwwwwwwwwwwwwwwwww

The increment and decrement operator ( ++ , -- ) increment and decrement numeric types by 1 .

The operator can either follow or precede the variable , depending on whether you want its value before or after the increment/decrement .

For example:

int x = 0, y = 0;
Console.WriteLine(x++); // Outputs 0; x is now 1
Console.WriteLine(++y); // Outputs 1; y is now 1

 

Specialized Operations on Integral Types

(The integral types are int , uint , long , ulong , short , ushort , byte , and sbyte )

 

Division

Division operations on integral types always truncate remainders ( round toward zero ) .

Dividing by a variable whose value is zero generates a runtime error ( a DivideByZeroException ) :

int a = 2/3; // 0

int b = 0;
int c = 5 /b; // throws DivideByZeroExceptyion

Dividing by the literal or constant 0 generates a compile-time error .

 

Overflow

At runtime , arithmetic operations on integral types can overflow .

By default , this happens silently -- no exception is thrown , and the result exhibits "wraparound" behaviour , as through the computation was done on a larger integer type and the extra significant bits discarded .

For example , decrementing the minimun possible int value results in the maximum possible int value:

int a = int.MinValue;
a--;
Console.WriteLine(a == int.MaxValue); // True

 

Overflow check operators

The checked operator tells the runtime to generate an OverflowException rather than overflowing silently when an integral-type expression or statement exceeds the arithmetic limits of that type .

The checked operator affects expressions with the ++ , -- , + , - ( binary and unary ) , * , / , and explicit conversion operators between integral types .

- The checked operator has no effect on the double and float types ( which overflow to special "infinite" values , as we will see soon ) and no effect on the decimal type ( which is always checked ) .

Checked can be used around either an expression or a statement block .

For example:

int a = 1000000;
int b = 1000000;

int c = checked(a*b); // Checked just the expression

checked 
{
...
c = a*b;
...
}

You can make arithmetic overflow checking the default for all expressions in a program by compiling with the /checked+ command-line switch ( in Visual Studio , go to Advance Build Settings ) .

If you then need to disable overflow checking just for specific expressions or statements , you can do so with the unchecked operator ,

For example , the following code will not throw exceptions -- even if compiled with /checked+:

int x = int.MaxValue;
int y = unchecked(x+1);

unchecked{ int z = x +1; }

 

Overflow checking for constant expressions

Regardless of the /checked compiler switch , expressions evaluated at compile time are always overflow-checked-unless you apply the unchecked operator:

int x = int.MaxValue+1; // Compile-time error
int y = unchecked(int.MaxValue+1); // No errors

 

Bitwise operators

C# supports the following bitwise operators:

3.jpg

 

8- and 16-Bit Integral Types

The 8- and 16-bit integral types are byte , sbyte , short and ushort .

These types lack their own arithmetic operator , so C# implicitly converts them to larger types a required .

This can cause a compile-time error when trying to assign the result back to a small integral type:

short x = 1 , y =1;
short z = x+y; // Compile-time error

In this case , x and y are implicitly converted to int so that the addition can be performed .

This means the result is also an int , which cannot be implicitly cast back to a short ( because it could cause loss of data ) .

To make this compile , we must add an explicit cast:

short  z  =  (short)(x+y); // OK

 

Special Float and Double Values

Unlike integral types , floating-point types have values that certain operations treat specially .

These special value are NaN (Not a Number) ,  +∞ , -∞ , and -0 .

The float and double classes have constants for NaN , +∞ , and -∞ , as well as other values ( MaxValue , MinValue , and Epsilon ) . 

For example:

Console.WriteLine(double.NegativeInfinity); // -Infinity

The constants that represent special values for double and float are as follow:

4.jpg

Dividing a nonzero number by zero results in an infinite value .

For example:

Console.WriteLine(  1.0 / 0.0); //   Infinity
Console.WriteLine(-1.0  / 0.0); // -Infinity
Console.WriteLine(  1.0 /-0.0); // -Infinity
Console.WriteLine(-1.0 / -0.0); //  Infinity

Dividing zero by zero , or subtracting infinity from infinity , results in a NaN .

For example:

Console.WriteLine(0.0/0.0); // NaN
Console.WriteLine((1.0/0.0)-(1.0/0.0)); // NaN

 

When using == , a NaN value is never equal to another value , even another NaN value:

Console.WriteLine( 0.0/0.0  ==  double.NaN );  // False

To test whether a value is NaN , you must use the float.NaN or double.IsNaN method:

Console.WriteLine(object.Equals( 0.0/0.0 , double.NaN)); //True

- NaNs are sometimes useful in representing special values .

In WPF , double.NaN represents a measurement whose value is "Automatic" .

Another way to represent such a value is with a nullable type ( Chapter 4 ); another is with a custom struct that wraps a numeric type and adds an additional field ( Chapter 3 ) .

 

float and double follow the specification of the IEEE 754 format types , supported natively by almost all processors .

You can find detailed information on the behavior of these types at > http://www.ieee.org

 

double Versus decimal

double is useful for scientific computations ( such as computing spatial coordinates ) .

decimal is useful for financial computations and values that are "man-made" rather than the result of real-world measurements .

Here's a summary of the differences:

5.jpg

Real Number Rounding Errors

float and double internally represent numbers in base 2 .

For this reason , only numbers expressible in base 2 are represented precisely .

Practically , this means most literals with a fractional component ( which are in base 10 ) will not be represented precisely .

For example:

float tenth = 0.1f; // Not quite 0.1
float one = 1f;
Console.WriteLine(one-tenth*10f); // -1.490116E-08

This is why float and double are bad for financial calculations .

In contrast , decimal works in base 10 and so can precisely reprsent numbers expressible in base 10 ( as well as its factors , base 2 and base 5 ) .

Since real literals are in base 10 , decimal can precisely represent numbers such as 0.1 .

However , neither double nor decimal can precisely represent a fractional number whose base 10 representation is recurring:

decimal m = 1M/6M;    // 0.1666666666666666666666666667M
double  d = 1.0/6.0;  // 0.16666666666666666

This leads to accumulated rounding errors:

decimal notQuiteWholeM = m+m+m+m+m+m; // 1.0000000000000000000000000002M
double  notQuiteWholeD = d+d+d+d+d+d; // 0.99999999999999989

which breaks equality and comparison operations:

Console.WriteLine(notQuiteWholeM == 1M); // False
Console.WriteLine(notQuiteWholeD  < 1.0);// True

 

Boolean Type and Operators

C#'s bool type ( aliasing the System.Boolean type ) is a logical value that can be assigned the literal true or false .

Althorugh a Boolean value requires only one bit of storage , the runtime will use one byte of memory , since this is the minimum chunk that the runtime and processor can efficiently work with .

To avoid space inefficiency in the case of arrays , the Framework provides a BitArray class in the System .

Collections namespace that is designed to use just one bit per Boolean value .

 

Bool Conversions

No casting conversions can be made from the bool type to numeric types or vice versa .

 

Equlity and Comparison Operators

== and != test for equality and inequality of any type , but always return a bool value .

Value types typically have a very simple notion of equality:

int x = 1;
int y = 2;
int z = 1;
ConsoleWriteLine(x == y); // False
ConsoleWriteLine(x == z); // True

For reference types , equality , by default , is based on reference , as opposed to the actual value of the underlying object ( more on this in Chapter 6 ):

public class Dude
{
public string Name;
public Dude(string n){Name = n;}
}

...

Dude d1 = new Dude("John");
Dude d2 = new Dude("John");
Console.WriteLine(d1 == d2); // False
Dude d3 = d1;
Console.WriteLine(d1 == d3); // True

The equlity and comparison operators , == , != , < , > , >= , and <= , work for all numeric types , but should be used with caution with real numbers ( as we saw in "Real Number Rounding Errors" ) .

The comparison operators also work on enum type members , by comparing their underlying integral-type values .

We decribe this in "Enums" on page.118 in Chapter 3 .

We explain the equality andcomparison operators in grater detail in "Operator Overloading" on page.198 in Chapter 4 , and in "Equality Comparison" on page282 and "Order Comparison" on page293 .

 

Conditional Operators

The && and || operators test for and and or conditions .

They are frequently used in conjunction with the ! operator , which express not .

In this example , the UseUmbrella method returns true if it's rainy or sunny ( to protect us from the rain or the sun ) , as long as it's not also windy ( since umbrellas are useless in the wind ):

static bool UseUmbrella(bool rainy,bool sunny,bool windy)
{
return !windy && (rainy || sunny );
}

The && and || operators short-circuit evaluation when possible .

In the preceding example , if it is windy , the expression ( rainy || sunny ) is not even evaluated .

Short-circuiting is essential in allowing expressions such as following to run without throwing a NullReferenceException:

if( sb != null && sb.length>0 ) ...

The & and | operators also test for and and or conditons:

return  !windy & ( rainy | sunny );

The difference is that they do not short-circuit .

For this reason , they are rarely used in place of conditional operators .

- Unlike in C and C++ , the & and | operators perform ( non-short-circuiting ) Boolean comparisons when applied to bool expressions .

The & and | operators perform bitwise operations only when applied to numbers .

 

Conditional operator ( ternary operator )

The conditional operator ( more commonly called the ternary operator , as it's the only operator that takes three operands ) has the form  q?a:b;  , where if condition q is true , a is evaluated , else b is evaluated .

For example:

static int Max(int a,int b)
{
return (a>b)?a:b;
}

The conditional operator is particularly useful in LINQ queries ( Chapter 8 ) .

 

Strings and Characters

C#'s char type ( aliasing the System.Char type ) represents a Unicode character and occupies 2 bytes .

A char literal is specified inside single quotes:

char c = 'A'; // Simple character

Escape sequences express characters that cannot be expressed or interpreted literally .

An escape sequence is a backslash followed by a character with a special meaning .

For example:

char newLine    = '\n';
char backSlash  = '\\';

The escape sequence characters are shown in Table .

6.jpg

The \u ( or \x ) escape sequence lets you specify any Unicode character via its four-digit hexadecimal code:

char copyrightSymbol   = '\u00A9';
char omegaSymbol       = '\u03A9';
char newLine                 = '\u000A';

 

Char Conversions

An impicit conversion from a char to a numeric type works for the numeric types that can accommodate an unsigned short .

For other numeric types , an explicit conversion is required .

 

String Type

C#'s string type ( aliasing the System.String type , covered in depth in Chapter 6 ) represents an immutable sequence of Unicode characters .

Astring literal is specified inside double quotes:

string a = "Heat";

- string is a reference type , rather than a value type .

Its equaluty operators , however , follow value-type semantics:

string a = "test";
string b = "test";
Console.WriteLine(a == b); // true

The escape sequences that are valid for char literals also work inside strings:

string a = "Here is a tab:\t";

The cost of this is that whenever you need a literal backlash , you must write it twice:

string a1 = "\\\\server\\fileshare\\helloworld.cs";

To avoid this problem , C# allows verbatim string literals .

A verbatim string literal is prefixed with @ and does not support escape sequences .

The following verbatim string is identical to the preceding one:

string a2 = @"\\server\fileshare\\helloworld.cs";

A verbatim string literal can also span multiple lines:

string escaped  = "First Line\r\nSecond Line";
string verbatim = @"First Line
Second Line";

// True if your IDE uses CR-LF line separators:

Console.WriteLine(escaped == verbatim);

You can include the double-quote character in a verbatim literal by writing it twice:

string xml = @"<customer id=""123""></customer>";

 

String concatenation

The + operator concatenates two strings:

string s = "a"+"b";

One of the operands may be a nonstring value , in which case ToString is called on that value .

For example:

string s = "a"+5; // a5

Using the + operator repeatdly to build up a string is inefficent: a better solution is to use the System.Text.StringBuilder type ( described in Chapter 6 ) .

 

String interpolation (C#6)

A string preceded with the $ character is called an interpolated string .

Interpolated strings can include expressions inside braces:

int x = 4;
Console.WrtieLine($"A square has {x} sides"); // Prints: Asquare has 4 sides

Any valid C# expression of any type can appear within the braces , and C# will convert the expression to a string by calling its ToString method or equivalent .

You can change the formatting by appending the expression with a colon and a format string ( format strings are descreibed in "String.Format and composite format strings" on page 234 ) :

string s = $"255 in hex is {byte.MaxValue:X2}"; 
// X2 = 2-digit Hexadecimal
// Evaluates to "255 in hex is FF"

Interpolated strings must complete on a single line , unless you also specify the verbatim string operator .

Note that the $ operator must come before @:

int x = 2'
string s = $@"this spans{
x} lines";

To include a brace literal in an interpolated string , repeat the desired brace character .

 

String comparisons

string does not support < and > operators for comparisons .

You must use the string's CompareTo method , described in Chapter 6 .

 

 

Arrays

An array represents a fixed number of variables ( called elements ) of a particular type .

The elements in an array are always stored in a contiguous block of memory , providing highly efficient access .

An array is denoted with square brackets after the element type .

For example:

char[ ] vowels = new char[5];  //  Declare an array of 5 characters

Square brackets also index the array , accessing a particular element by position:

vowels[0] = 'a';
vowels[1] = 'e';
vowels[2] = 'i';
vowels[3] = 'o';
vowels[4] = 'u';

Console.WriteLine(vowels[1]);  // e

This prints "e" because array indexes start at 0 .

We can use a for loop statement to iterate through each element in the array .

The for loop in this example cycles the integer i from 0 to 0.4:

for(int i=0;i<vowels.Length;i++)
Console.WriteLine(vowels[i]); // aeiou

The Length property of an array returns the number of elements in the array .

Once an array has been created , its length cannot be changed .

The System.Collection namespace and subnamespaces provide higher-level data structures , such as dunamically sized arrays and dictionaries .

An array initialization expression lets you declare and populate an array in a single step:

char[ ] vowels = new char[ ]{'a','e','i','o','u'};

or simply:

char[ ] vowels = {'a','e','i','o','u'};

All arrays inherit from the System.Array class , providing common serivces for all arrays .

These members include methods to get and set elements regardless of the array type , and are described in Chapter 7 .

 

Default Element Initialization

Creating an array always preinitializes the elements with default values .

The default value for a type is the result if a bitwise zeroing of memory .

For example , consider creating an array of integers .

Since int is a value type , this allocates 1,000 integers in one contiguous block of memory .

The default value for each element will be 0:

int[] a = new int[1000];
Console.WriteLine(a[123]); // 0

 

Value types versus reference types

Whether an array element type is a value type or a reference type has important performance implications .

When the element type is a value type , each element value is allocated as part of the array .

For example:

public struct Point{public int X,Y;}
...

Point[] a = new Point[1000];
int x = a[500].x; // 0

Had Point been a class , creating the array would have merely allocated 1,000 null references:

public class Point{ public int X,Y;}
...

Point[] a = new Point[1000];
int x = a[500].x; // Runtime error , NullReferenceException

To avoid this error , we must explicitly instantiate 1,000 Points after instantiating the array:

Point[] a = new Point[1000];
for(int i=0;i<a.Length;i++) // Iterate i from 0 to 999
a[i] = new Point(); // Set array element i with new point

An array itself is always a reference type object , regardless of the element type .

For instance , the following is legal:

int[ ] a = null;

 

Multidimensional Arrays

Multidimensional arrays come in two varieties: rectangular and jagged .

Rectangular arrays represent an n-dimensional block of memory , and jagged arrays are arrays of arrays .

 

Rectangular arrays

Rectangular arrays are declared using commas to separate each dimension .

The following declaires a rectangular two-dimensional array , where the dimensions are 3 by 3:

int[,] martix = new int[3,3];

The GetLength method of an array returns the length for a given dimension ( starting at 0 ):

for(int i=0;i<matrix.GetLength(0);i++){
   
   for(int j=0;j<matrix.GetLength(1);j++){

    martrix[i,j] = i*3+j;

A rectangular array can be initialized as follows ( to create an array identical to the previous example ):

int[,] martrix = new int[,]
{
  {0,1,2},
  {3,4,5},
  {6,7,8}

};

Jagged arrays

Jagged arrays are declared using successive square brackets to represent each dimension .

Here is an example of declaring a jagged two dimensional array , where the outermost dimension is 3:

int [ ] [ ] matrix = new int [3] [ ];

- Interestingly , this is new int [ ] [3] and not new int [3] [ ] .

Eric Lippert has written an excellent article on why this is so . > http://albahari.com/jagged

The inner dimensions are not specified in thr declaration because , unlike a rectangular array , each inner array can be an arbitrary length .

Each inner array is implicitly initialized to null rather than an empty array .

Each inner array must be created manually:

for(int i=0;i<matrix.Length;i++)
{
   matrix[i] = new int[3];     // create inner array
    
    for(int j=0;j<matrix[i].Length;j++)
    {
       martrix[i][j] = i*3+j;
    }    


}

A jagged array can be initialized as follows ( to create an array identical to the previous example with an additional element at the end ):

int[][] matrix = new int[][]
{
   new int[]{0,1,2},
   new int[]{3,4,5},
   new int[]{6,7,8,9}
};

 

Simplified Array Initialization Expressions

These are two ways to shorten array initialization expressions .

The first is to omit the new operator and type qualifications:

char[] vowels = {'a','e','i','o','u'};

int[,] rectangularMatirx =
{
    {0,1,2},
    {3,4,5},
    {6,7,8}
};

int[][] jaggedMatrix =
{
    new int[]{0,1,2},
    new int[]{3,4,5},
    new int[]{6,7,8}
}

The second approach is to use the var keyword ,which tells the compiler to inplicitly type a local variable:

var i = 3;                  // i is impicitly of type int
var s = "sausage";   // s is implicitly type of string

// therefore:

var rectMatrix = new int[,] // recMatrix is implicitly of type int[,]
{
    {0,1,2},
    {3,4,5},
    {6,7,8}
};

var jaggedMat = new int[][]
{
    new int[]{0,1,2},
    new int[]{3,4,5},
    new int[]{6,7,8}
}

 

Implicit typing can be taken one stage further with arrays: you can omit the type qualifier after the new keyword and have the compiler infer the array type:

var vowels = new [ ] {a,e,i,o,u};  //  compiler infers char[ ]

For this to work , the elements must all be implicitly convertible to single type ( and at least one of the elements must be of that type , and there must be exactly one best type ) .

For example:

var x = new [ ] {1,10000000000};  //  all convertible to long 

 

Bounds Checking

All arrays indexing is bounds-checked by the runtime .

An IndexOutOfRangeExecption is thrown if you use an invalid index:

int[] arr = new int[3];
arr[3] = 1; // IndexOutOfRangeException thrown

As with Java , array bounds checking is necessary for type safety and simplifies debugging .

- Generally , the performance hit from bounds checking is minor , and the JIT( Just-In-Time ) compiler can perform optimizations , such as  determining in advance whether all indexes will be safe before entering a loop , thus avoiding a check on each iteration .

In addition , C#  provides "unsafe" code that can explicitly bypass bounds checking ( see "Unsafe Code and Pointers" on page 201 in Chapter 4 ) .

 

Variable and Parameters

A variable represents a storage location that has a modifiable value .

A variable can be a local variable , parameter ( value , ref , or out ) , field ( instance or static ) , or array element .

 

The Stack and Heap

The stack and the heap are the places where variables and constants reside .

Each has very different lifetime semantics .

 

Stack

The stack is a block of memory for string local variables and parameters .

The stack logically grows and shrinks as a function is entered and exited .

Consider the following method ( to avoid distraction , input argument checking is ignored ):

static int Factorial(int x)
{

if(x==0)return 1;

return x*Factorial(x-1);

}

This method is recursive , meaning that it calls itself .

Each time the method is entered , a new int is allocated on the stack , and each time the method exists , the int is deallocated .

 

Heap

The heap is a block of memory in which objects ( i.e. , reference-type instances ) reside .

Whenever a new object is created , it is allocated on the heap , and a reference to that object is returned .

During a program's execution , the heap starts filling up as new objects are created .

The runtime has a garbage collector that periodically dealocates objects from the heap , so your program does not run out of memory .

An object is eligible for deallocation as soon as its not referenced by anything that's itself "alive" .

In the following example , we start by creating a StringBuilder object referenced by the variable ref1 , and then write out its content .

That StringBuilder object is then immediately eligible for garbage collection , because nothing subsequently use it .

Then , we create another StringBuilder referenced by variable ref2 , and copy that reference to ref3 .

Even though ref2 is not used after that point , ref3 keeps the same StringBuilder object alive -- ensuring that it doesn't become eligible for collection until we've finished using ref3 .

using System;
using System.Text;

class Test
{
    static void Main()
    {
        StringBuilder ref1 = new StringBuilder("object1");
        Console.WriteLine(ref1);
        // the StringBuilder referenced by ref1 is now eligible for GC
        
        StringBuilder ref2 = new StringBuilder("object2");
        StringBuilder ref3 = ref2;
        // the StringBuilder refereneced by ref2 is NOT yet eligible for GC
        
        Console.WriteLine(ref3);  // object2
        
    }
}

 

Value-type instances (  and object reference ) live whereever the variable was declared .

If the distance was declared as a field within a class type , or as an array element , the instance lives on the heap .

- You can't explicitly delete objects in C# ,  as you can in C++ .

An unreferenced object is eventually clooected by the garbage collector .

The heap also stores static fields .

Unlike objects allocated on the heap ( which can get garbage-collected ) , these live until the application domain it torn down .

 

Definite Assignment

C# enforces a definite assignment policy .

In practice , this means that outside of an unsafe context , it's impossible to access uninitialized memory .

Definite assignment has three implications:

  • Local variables must be assigned a value before they can be read .
  • Function arguments must be supplied when a method is called ( unless marked as optional -- see "Optional parameter" on page 53 ).
  • All other variables ( such as fields and array elements ) are automatically initialized by the runtime .

For example , the following code results in a compile-time error:

static void Main()
{
int x;
Console.WriteLine(x); // Compile-time error
}

Fields and array elements are automatically initialized with the default values for their type .

The following code outputs 0 , because array elements are implicitly assigned to their default values:

static void Main()
{
int[] ints = new int[2];
Console.WriteLine(int[0]); // 0
}

The following ocde outputs 0 , because fields are implicitly assigned a default value:

class Test
{
  static int x;
  static void Main(){Console.WriteLine(x);} // 0
}

 

Default Values

All type instances have a default value .

The default value for the predefined types is the result of a bitwise zeroing of memory:

7.jpg

You can obtain the default value for any type with the default keyword ( in practice , this is useful with generics , which we'll cover in Chapter 3 ) :

decimal  d  =  default (decimal);

The default value in a custom value type ( i.e. struct ) is the same as the default value for each field defined by the custom type .

 

Parameters

A method has a sequence of parameters .

Parameters define the set of arguments that must be provided for that method .

In this example , the method Foo has a single parameter named p , of type int:

static void Foo(int p)
{
p = p+1; // increment p by 1
Console.WriteLine(p); // write p to screen
}

static void Main()
{
Foo(8); // call Foo with an argument of 8 
}

You can control how parameters are passed with the ref and out modifiers:

8.jpg

 

Passing arguments by value

By default , arguments in C# are passed by value , which is by far the most common case .

This means a copy of value is created when passed to the method:

class Test
{

    static void Foo(int p)
    {
        p = p+1; // increment p by 1
        Console.WriteLine(p); // write p to screen
    }
    
    static void Main()
    {
        int x = 8;
        Foo(x); // make a copy of x 
        Console.WriteLine(x); // x will still be 8
    }

}

Assigning p a new value does not chanege the contents of x , since p and x reside in different memory locations .

Passing a reference-type argument by value copies the reference , but not the object .

In the following example , Foo sees the same StringBuilder object thst Main instantiated , but has an independent reference to it .

In order words , sb and fooSB are separate variables that reference the same StringBuilder  object:

class Test
{

    static void Foo(StringBuilder fooSB)
    {
        fooSB.Append("test");
        fooSB = null;
    }
    
    static void Main()
    {
        StringBuilder sb = new StringBuilder();
        Foo(sb);
        Console.WriteLine(sb.ToString());
    }

}

Because fooSB is a copy of a reference , setting it to null does not make sb null .

( If , however , fooSB was declared and called with the ref modifier , sb would become null ) .

 

The ref modifier

To pass by reference , C# provides the ref parameter modifier .

In the following example , p and x refer to the same memory locations:

class Test
{

    static void Foo(ref int p)
    {
        p = p+1; // increment p by 1
        Console.WriteLine(p); // write p to screen
    }
    
    static void Main()
    {
        int x = 8;
        Foo(ref x); // ask Foo to deal directly with x
        Console.WriteLine(x); // x is now 9
    }

}

Now assigning p a new value changes the contents of x .

Notice how the ref modifier is required both when writing and calling the method .

This makes it very clear what's going on .

The ref modifier is essential in implementing a swap method ( later , in "Generics" on apge 122 in Chapter3 , we will show how to write a swap method that works with any type ):

class Test
{
    static void Split(ref string a,ref string b)
    {
        string temp = a;
        a = b;
        b = temp;
    }
    
    static void Main()
    {
        string x = "Penn";
        string y = "Teller";
        
        Swap(ref x,ref y);
        
        Console.WriteLine(x); // Teller
        Console.WriteLine(y); // Penn
        
    }
}
- A parameter can be passed by refence or by value , regardless of whether the parameter type or a value type .

 

The out modifier

An out argument is like a ref argument , except it:

  • Need not be assigned before going into the function
  • Must be assigned before it comes out of the function

The out modifier is most commonly used to get multiple return values back from a method .

For example:

class Test
{
    static void Split(string name,out string firstNames,out string lastName)
    {
        int i = name.LastIndexOf(' ');
        firstNames = name.Substring(0,i);
        lastName   = name.Substring(i+1);
    }
    
    static void Main()
    {
        string a,b;
        
        Split("Stevie Ray Vaughan",out a,out b);
        
        Console.WriteLine(a); // Steive Ray
        Console.WriteLine(b); // Vaughan
    }
}
Like a ref parameter , an out parameter is passed by reference .

 

Out variables and discards(C#7)

From C# 7 , you can declare variables on the fly when calling methods with out parameters.

We can shorten the Main method in our preceding example as follows:

static void Main()

{

    Split("Steive Ray Vaughan",out string a,out string b);

   

    Console.WriteLine(a); // Steive Ray

    Console.WriteLine(b); // Vaughan

   

}

When calling methods with multiple out parameters , sometimes you are not interested in receiving values from all the parameters .

In such cases , you can “discard” the ones you are uninterested in with an underscore .

Split("Steive Ray Vaughan",out string a,out _); // discard the 2nd param
Console.WriteLine(a);

In this case , the compiler treats the underscore as a special symbol , called a discard .

You can include multiple discards in a single call .

Assuming SomeBigMethod has been defined with seven out parameters , we can ignore all but the fourth as follows:

SomeBigMethod(out _,out _,out _,out int x,out _,out _,out _,);

For backward compatibility , this language feture will not take effect if a real underscore variable is in scope:

String _;
Split("Steive Ray Vaughan",out string a,_); // will not compile

 

Implications of passing by reference

When you pass an argument by reference , you alias the storage location of an existing variable rather than create a new storage location .

In the following example , the variables x and y represent the same instance:

class Test

{

    static int x;

   

    static void Main()

    {

         Foo(out x);

    }

   

    static void Foo(out int y)

    {

         Console.WriteLine(x);   // x is 0

         y = 1;                  // Mutate y

         Console.WriteLine(x);   // x is 1

    }

}

 

The params modifier

The params parameter modifier may be specified on the last parameter of a method so that the method accepts any number of arguments of a particular type .

The parameter type must be declared as an array .

For example:

class Test

{

    static int Sum(params int[] ints)

    {

         int sum = 0;

         for(int i=0;i<ints.Length;i++)

         {

             sum += ints[i]; // increase sum by ints[i]

            

         }

        

         return sum;

        

    }

   

    static void Main()

    {

         int total = Sum(1,2,3,4);

         Console.WriteLine(total);

    }

}

You can also supply a params argument as an odrinary array .

The first line in Main is semantically equivalent to this:

int  total  =  Sum(new int[]{1,2,3,4});

 

Optional parameters 

From C#4.0 , methods , constructors , and indexers(Chapter3) can declare optional parameters .

A parameter is optional if it specifies a default value in its declaration:

void Foo(int x = 23) {  Console.WriteLine(x);  }

Optional parameters may be omitted when calling the method:

Foo(); // 23\

The default argument of 23 is actually passed to the optional parameter x -- the compiler backes the value 23 into the compiled code at the calling side .

The preceding call to Foo is semantically identical to:

Foo(23); // 23

because the compiler simply substitutes the default value of an optional parameter whereever it is used .

- Adding an optional parameter to a public method that's called from another assembly requires recompilation of both assemblies -- just as though the parameter was mandatory .

The default value of an optional parameter must be specified by a constant expression , or a parameterless constructor of a value type .

Optional parameters cannot be marked with ref or out .

Mandatory parameters must occur before optional parameters in both method declaration and the method call ( the exception is with params arguments , which still always come last ) .

Un the following example , the explicit value of 1 is paseed to x , and the defualt value of 0 is passed to y:

void Foo(int x=0,int y=0)

{

    Console.WriteLine(x+", "+y);

}

 

void Test()

{

    Foo(1); // 1.0

}

To do the converse ( pass a default value to x and an explicit value to y ) you must combine optional parameters with named arguments .

 

Named arguments

Rather than idenifting an argument by position , you can identifu an argument by name .

For example:

void Foo(int x,int y)

{

    Console.WriteLine(x+", "+y);

}

 

void Test()

{

    Foo(x:1,y:2); // 1, 2

}

Named arguments can occur in any order .

The following calls to Foo are semantically identical:

Foo(x:1,y:2);

Foo(x:2,y:1);

- A subtle difference is that argument expressions are evaluated in the order in which they appear at the calling site .

In general , this makes a adifference only with interdependent side-effecting expressions such as the following , which writes 0, 1:

 

int a = 0;

Foo(y:++a,x:--a); // ++a is evaluated first

Of cause , you would almost certainly avoid writing such code in practice!

You can mix named and positional arguments:

Foo(1,y:2);

However , there is a restriction: positional arguments must come before named arguments .

So we could not call Foo like this:

Foo(x:1,2); // compiler-time error

Named arguments are particularly useful in conjunction with optional parameters .

For instance , consider the following code:

void Bar(int a=0,int b=0, int c=0, int d=0){...}

We can call this supplying only a value for d as follows:

Bar(d:3);

This is particularly useful when calling COM APIs , and is discussed in detail in "Native and COM Interoperability" on page221 .

 

Ref Locals(C#7)

C#7 adds an esoteric feature , whereby you can define a local variable that references and element in an array or field in an object:

int[] numbers = {0,1,2,3,4};

ref int numRef = ref number[2];

In this example , numRef is a reference to the number[2] .

When we modify numRef , we modify the array element:

numRef*=10;

Console.WriteLine(numRef);     // 20

Console.WriteLine(numbers[2]); // 20

The target for a ref loacal must be an array element , field , or local variable; it cannot be a property(Chapter3) .

Ref locals are intended for specialized micro-optimization scenarios , and are typically used in conjunction with ref returns .

 

Ref Returns(C#7)

You can return a ref local from a method .

This is called a ref return:

static string X ="Old Value";

static ref string GetX() => ref X; // this method returns a ref

 

static void Main()

{

    ref string xRef = ref GetX(); // assign result to a ref local

    xRef = "New Value"

    Console.WriteLine(X); // New Value

}

 

var -- Implicitly Typed Local Variables

It is often the case that you declare and initialize a variable in one step .

If the compiler is able to infer the type from the initialization expression , you can ise the keyword var ( include in C#3.0 ) in place of the type declaration .

For example:

var x = "hello";

var y = new System.Text.StringBuilder();

var z = (float)Math.PI;

This is precisely equivalent to :

string x = "hello";

System.Text.StringBuilder y = new System.Text.StringBuilder();

float z = (float)Math.PI;

Because of this direct equivalence , implicitly typed variables are statically typed .

For example , the following generates a compiler-time error:

var x = 5;

x = "hello"; // Compile-time error; x is of type int

- var can decrease code readability in the case when you can't deduce the type purely by looking at the variable declaration .

For example:

 

Random r = new Random();

var x = r.Next();

What type is x ?

In "Anonymous Types" on page181 in Chapter4 , we will describe a scenario where the use of var is mandatory .

 

Expressions and Operators

An expression essentially denotes a value .

The simplest kinds of expressions are constants and variables .

Expressions can be transformed and combined using operators .

An operator takes one or more input operands to output a new expression .

Here is an example of a constant expression:

   12

We can use the * operator to combine two operands ( the literal expressions 12 and 30 ) , as follows:

   12*30

Complex expressions can be built because and operand may itself be an expression , such as the operand (12*30) in the following example:

   1  +  (12*30)

Operators in C# can be classed as unary , binary , or ternary -- depending on the number of operands they works on ( one , two , or three ) .

The binary operators always use infix notation , where the operator is placed between the two operands .

 

Primary Expressions

Primary expressions include expressions composed of operators that are intrinsic to the basic plumbing of the language .

Here is an example:

Math.Log(1);

This expression is composed of two primary expressions .

The first expression performs a member-lookup ( with the . operator) , and the second expression performs a metod call ( with the () operator ) .

 

Void Expressions

A void expression is an expression that has no value .

For example:

Console.WriteLine(1)

A void expression , since it has no value , cannot be used as an operand to build more complex expressions:

1 + Console.WriteLine(1) // compile-time error

 

Assignment Expressions

An assignment expression uses the = operator to assign the result of another expression to a variable .

For example:

x = x*5

An assignment expression is not a void expression -- it has a value of whatever was assigned , and so can be incorporated into another expression .

In the following example , the expression assigns 2 to x and 10 to y:

y = 5*(x=2)

This style of expression can be used to initialize multiple values:

a = b = c = d = 0

The compound assignment operators are syntactic shortcuts that combine assignment with another operator .

For example:

x *= 2// equivalent to x = x*2;

x <<= 1; // equivalent to x = x<<1;

( A subtle exception to this rule is with events , which we describe in Chapter4: the += and -= operators here  are treated specially and map to the event's add and remove accessors ) .

 

 

Operator Precedence and Associativity

When an expression contains multiple operators , precedence and associativity determine the order of their evaluation .

Operators with higher precedence execute before operators of lower precedence .

If the operators have the same precedence , the operator's associativity determines the order of evalution .

 

Precedence

The following expression:

1+2*3

is evaluated as follows because * has a higher precedence than +:

1+(2*3)

 

Left-associative operators 

Binary operators ( except for assignment , lambda , and null coalecing operators ) are left-associative; in other words , they are evaluated form left to right .

For example:

8 / 4 / 2

is evaluated as follows due to left associativity:

( 8 / 4 ) / 2  // 1

You can insert parentheses to change the actual order of evaluation:

8 /  ( 4 / 2 )  // 4

 

Right-associative operators 

The assignment operators , lambda , null coalescing , and conditional operator are right-associative; in other words , they are evaluated from right to left .

Right associativeity allows multiple assignments such as the following to compiler:

x = y = 3;

The first assigns 3 to y , and then assigns the result of that expression (3) to x .

 

Operator Table

It lists C#'s operators in order of precedence .

Operators in the same category have the same precedence .

We explain user-overloadable operators in "Operator Overloading" on page198 in Chapter4 .

 

 

Null operators

C# provides two operators to make ot easier to work with nulls: the null coalescing operator and the null-conditional operator .

 

Null Coalescing Operator

The ?? operator is the null coalecing operator .

It says " If the operand is non-null , give it to me; otherwise , give me a default value " .

For example:

string s1 = null;

string s2 = s1 ?? "nothing"; // s2 evaluates to "nothing"

If the lefthand expression is non-null , the righthand expression is never evaluated .

The null coalescing operator also works with nullable value types ( see "Nullable Types" on page 173 in Chapter4 ) .

 

Null-conditional Operator(C#6)

The ?. operator is the null-conditional or "Elvis" operator ( after the Elvis emotiocon ) , and is new to C#6 .

It allows you to call methods and access members just like the standard dot operator , except that if the operand on the left is null , the expression evaluates to null instead of throwing a NullReferenceException:

System.Text.StringBuilder sb = null;

string s = sb?.ToString(); // No error; s instead evaluates to null

The last line is equivalent to:

string s = (sb == Null? null:sb.Tostring());

Upon encountering a null , the Elvis operator short-circuits the remainder of the expression .

In the following example , s evaluates to null , even with a standard dot operator between ToString() and ToUpper():

System.Text.StringBuilder sb = null;

string s = sb?.ToString().ToUpper(); // s evaluates to null without error

Repeated use of  Elvis is necessary only if the operand immediately to its left may be null .

The following expression is robust to both x being null and x.y being null:

x?.y?.z

and is equivalent to the following ( excet that x.y is evaluated only once ):

x==null?null:(x.y==null?null:x.y.z);

The final expression must be capable of accepting a null .

The following is illegal:

System.Text.StringBuilder sb = null;

int length = sb?.ToString().Length; // illegal : int cannot be null

We can fix this with the use of nullable value types ( see "Nullable Types" on page173 in Chapter4):

If you've already familiar with nullable types , here is a preview:

int? length = sb?.ToString().Length; // OK : int? can be null

You can also use the null-conditional operator to call a void method:

someObject?.SomeVoidMethod();

If someObject is null , this becomes a "no-operation" rather than throwing a NullReferenceException .

The null-conditional operator can be used with the commonly used type members that we describe in Chapter3 , including methods , fields , properties and indexers .

It also combines well with the null coalescing operator:

System.Text.StringBuilder sb = null;

string s = sb?.ToString()??"nothing"; // s evaluates to "nothing"

 

Statements

Functions comprise statements that execute sequentailly in the textual order in which they appear .

A statement block is a series of statements appearing between braces ( the {} tokens ) .

 

Declaration Statements

Declaration statement declares a new variable , optionally initializeing the variable with an expression .

A declaration statement ends in a semicolon .

You may declare multiple variables of the same type in a comma-separated list .

For example:

string someWord = "rosebud";

int someNumber = 42;

bool rich = true ,famous = false;

A constant declaration is like a variable declaration , ex cept that it cannot be changed after it has been declared , nd the initialization must occur with the declaration ( see "Constants" on page91 in Chapter3 ):

const double c = 2.99792458E08;

c+=10; // compiler-time error

 

Local variables

The scope of a local variable or loacl constant extends throughout the current block .

You cannot declare another local variable with the same name int the current block or in any nested blocks .

For example:

static void Main()

{

    int x;

    {

         int y;

         int x; // error - x already defines

    }

    {

         int y; // ok - y not in scope

    }

   

    Console.WriteLine(y); // error - y is out of scope

}aaaa

- A variable's scope extends in both directions throughout its code block .

This means that if wi moved the initial declaration of x int this example to the bottom of t

he method , we would get the same error .

This is in contrast to C++ and is somewhat peculiar , given that it is not legal to refer to a variable or constant before it has declared .

 

Expression Statements

Expression statements are expressions that are also valid statements .

An expression statement must either change state or call something that might change state .

Changeing state essentially means changing a variable .

The possible expression statements are:

  • Assignment expressios (  including increment and descrement and decrement expressions ) 
  • Metod call  expressions ( both void and nonvoid )
  • Objects instantiation expressions

Here are some examples:

// declare variables with declaration statements:

string s;

int x,y;

System.Text.StringBuilder sb;

 

// expression statements

x = 1+2;                   // assignment expression

x++;                       // increment expression

y = Math.MAX(x,5);             // assignment expression

Console.WriteLine(y);     // method call expression

sb = new StringBuilder(); // assignment expression

new StringBuilder();      // object instantiation expression

When you call a  constructor or a method that returns a value , you're not obliged to use the result .

However , unless the constructor or method changes state , the statement is completely useless:

new StringBuilder();  // legal , but useless

new string('c',3);        // legal , but useless

x.Equals(y);          // legal , but useless

 

Selection Statements

C# has the following mechanisms to conditionally control the flow of program execution:

  • Selection statements ( if , switch )
  • Conditional operator ( ?: )
  • Loop statements ( while , do...while , for , foreach )

This section covers the simplest two constructs: the if-else statement and the switch statement .

 

The if statement

An if statement executes a statement if a bool expression is true .

For example:

if(5<2*3)Console.WrtieLine("true"); // true

The statement can be a code block:

if(5<2*3)

{

    Console.WriteLine("true");

    Console.WriteLine("Let's move on");

}

 

The else clause

An if statement can optionally feature an else clause:

if(2+2==5)

{

    Console.WriteLine("Does not compute");

}else

{

    Console.WriteLine("flase"); // false

}

Within an else clause , you can nest another if statement:

if(2+2==5)

{

    Console.WriteLine("Does not compute");

}else

{

    if(2+2==4)

    {

    Console.WriteLine("computes"); // computes

    }

}

 

Changing the flow of execution with braces

An else clause always applies to the immediately preceding if statement in the statement block .

For example:

if(true)

    if(false)

    Console.WriteLine();

    else

    Console.WriteLine("executes");

This is semantically identical to:

if(true)

{

    if(false)

         Console.WriteLine();

    else

         Console.WriteLine("executes");

}

We can change the execution flow by moving the braces:

if(true)

{

    if(false)

         Console.WriteLine();

}else

    Console.WriteLine("does not execute");

With braces , you explicitly state your intention .

This can improve the readability of nested if statements -- even when not required by the compiler .

A notable exception is with the following pattern:

static void TellMeWhatICanDo(int age)

{

    if(age>=35)

    Console.WriteLine("you can be president!");

    else if(age>=21)

    Console.WriteLine("you can drink!");

    else if(age>=18)

    Console.WriteLine("you can vote!");

    else

    Console.WriteLine("you can wait");

}

Here , we've arranged the if and else statements to mimic the "elseif" construct of other language ( and C#'s #elif preprocessor directive ) .

Visual Studio's autoformatting recognizes this pattern and preserves the indentation .

Semantically , though , each if statement following an else statement is functionally nested within the else clause .

 

The Switch statement

switch statements let you branch program execution based on a selection if possible values that a variable may have .

switch statements may result in cleaner code than multiple if statements , since switch statements require an expression to be evaluated only onece .

For instance:

static void ShowCard(int cardNumber)

{

    switch(cardNumber)

    {

         case 13:

             Console.WriteLine("King");

             break;

         case 12:

             Console.WriteLine("Queen");

             break;

         case 11:

             Console.WriteLine("Jack");

             break;

         case -1:                                // Joker is -1

             goto case 12;                      // in this game koker counts as queen

         default:                                // executes for any other cardNumber

             Console.WriteLine("cardNumber");

             break;

    }

}

This example demonstrates the most common scenario , which is switching on constants .

When you specify a constant ,  you're restricted to the built-in integral types , bool , char , enum types , and the string type .

At the end of each clause , you must say explicitly where execution is to go next , with some kind of jump statement ( unless your code ends in an infinite loop ) .

Here are the options:

  • break ( jumps to the end of the switch statement )
  • goto case x ( jumps to another case clause )
  • goto default ( jumps to the default clause )
  • Any other jump statement -- namely , return , throw , continue , or goto label

When more than one value should execute the same code , you can list the common casessequentially:

switch(cardNumber)

{

    case 13:

    case 12:

    case 11:

         Console.WriteLine("face card");

         break;

    default:

         Console.WriteLine("plain card");

         break;

}

This feature of a switch statement can be pivotal in terms of producing cleaner code than multiple if-else statements .

 

The switch statement with patterns(C#7)

From C#7 , you can also switch on types:

static void Main()

{

    TellMeTheType(12);

    TellMeTheType("hello");

    TellMeTheType(true);

}

 

static void TellMeTheType(object x) // object allows any type

{

    switch(x)

    {

         case int i:

             Console.WriteLine("it's a int!");

             Console.WriteLine($"the suqre of {i} is {i*i}");

             break;

         case string s:

             Console.WriteLine("it's a string");

             Console.WriteLine($"the length of {s} is {s.Length}");

             break;

         default:

             Console.WriteLine("I don't know what x is");

             break;

        

    }

}

( The object type allows for a variable of any type; we discuss this fully in "Inheritance" on page96 and "The object Type" on page105 in Chapter3 ) .

Each case clause specifies a type upon which to match , and a variable upon which to assign the typed value if the match succeeds ( see the "pattern" variable ) .

Unlike with constants , there is no restriction on what types you can use .

You can predicate a case with the when keyword:

switch(x)

{

    case bool b when b==true: // fires only when b is true

    Console.WriteLine("true");

    break;

   

    case bool b:

    Console.WriteLine("false");

    break;

}

The order of the case clauses can matter when switching on type ( unlike when switching on constants ) .

This example would give a different result if we reserved the two cases ( in fact , it would not even compiler , because the compiler would determine that the second case is unreachable ) .

An exception to this rule is the default clause , which is always executed last , regardless of where it appears .

You can stack multiple case clauses .

The Console.WriteLine in the following code will execute for any floating-point type grater than 1000:

switch(x)

{

    case float f when f>1000:

    case double d when d>1000:

    case decimal m when m>1000:

         Console.WriteLine("we can refer to x here but not f or d or m");

         break;

}

In this example , the compiler lets us consume the pattern variables f , d , and m , only in the when clauses .

When we call Console.WriteLine , it's unknown as to which one of those three variables will be assigned , so the compiler puts all of them out of scope .

You can mix and match constants and patterns in the same switch statement .

And you can also switch on the null value:

case null:

    Console.WriteLine("Nothing here");

    break;


Iteration Statements

C# enables a sequence of statements to execute repeatedly with the while , do-while , for , and foreach statements .

 

while and do-while loops

while loops repeatedly execute a body of code while a bool expression is true .

The expression is tested before the body of the loop is executed .

For example:

int i = 0;

while(i<3)

{

    Console.WriteLine(i);

    i++;

}

 

// OUTPUT:

// 0

// 1

// 2

do-while loops differ in functionality from while loops only in that they test the expression after the statement block has executed ( ensuring that the block is always executed at least once ) .

Here's the preceding example rewritten with a do-while loop:

int i = 0;

do

{

    Console.WrtieLine(i);

    i++;

}while(i < 3);

 

for loops 

for loops are like while loops with special clauses for initialization and iteration of a loop variable .

A for loop contains three clauses as follows:

for(initialization-clause;condition-clause;iteration-clause)

     statement-or-statement-block

  • Initialization clause

    -    Executed before the loops begins; used to initailize one or more iteration variables .

  • Condition clause

    -    The bool expression that , while true , will execute the body .

  • Iteration clause

    -    Executed after each iteration of the statment block; used typically to update the iteration variable .

For example , the following prints the numbers 0 through 2:

for(int i=0;i<3;i++)

Console.WriteLine(i);

The following prints the first 10 Fibonacci numbers ( where each number is the sum of the previous two ):

for(int i=0,prevFib=1,curFib=1;i<10;i++)

{

    Console.WriteLine(prevFib);

    int newFib = prevFib+curFib;

    prevFib = curFib;

    curFib = newFib;

}

Any of the three parts of the for statement may be omitted .

Once can implement an infinite loop such as the following ( through while(true) may be used insted ):

for(;;)

    Console.WriteLine("interrupt me");

 

foreach loops

The foreach statement iterates over each element in an enumerable object .

Most of the types in C# and the .NET Framework that represent a set of list of elements are enumerable .

For example , both an array and a string are enumerable .

Here is an example of enumerating over the characters in a string , from the first character through to the last:

foreach(char c in "beer") // c is the iteration variable

    Console.WriteLine(c);

   

// OUTPUT:

// b

// e

// e

// r

We define enumerable objects in "Enumeration and Iterators" on page167 in Chapter4 .

 

Jump Statements

The C# jump statements are break , continue , goto , return , and throw .

- Jump statements obey the reliability rules of try statements (see "try Statements and Exceptions" on page158 in Chapter4 ) .

This means that:

  • A jump out of a try block always executes the try's finally block before reaching the target of the jump .
  • A jump cannot be made from the inside to the outside of a finally block ( except via throw ) .

 

The break statement

The break statement ends the execution of the body of an iteration ir switch statement:

int x=0;

while(true)

{

    if(x++ > 5)

    {

         break; // break from the loop 

    }

}

//execution continues here after break

...

 

The continue statement

The continue statement forgoes the remaining statements in a loop and makes an early start on the next iteration .

The following loop skips even number:

for(int i=0;i<10;i++)

{

    if(i%2 == 0) // if i is even,

    {

         continue;    // continue with next iteration

    }

    Console.Write(i+" ");

}

 

//  OUTPUT:1 3 5 7 9

 

The goto statement

The goto statement transfers execution to another label within a statement block .

The form is as follows:

goto statement-label;

Or , when used within a switch statement:

goto case case-constant;  // ( Only works with constants , not patterns )

Alabel is a placeholder in a code block that precedes a statement , denoted with a colon suffix .

The following iterates the numbers 1 through 5 , mimicking a for loop:

int i = 1;

startloop:

if(i<=5)

{

    Console.Write(i+" ");

    i++;

    goto startloop;

}

 

//  OUTPUT:1 2 3 4 5

The goto case case-statement transfers execution to another case in a switch block ( see "The switch statement" on page65 ) .

 

The return statement

The return statement exits the method and must return an expression of the method's return type if the method is nonvoid:

static decimal AsPercentage(decimal d)

{

    decimal p = d*100m;

    return p;    // return ti the calling method with value

}

A return statement can appear anywhere in a method ( except in a finally block ) .

 

The throw statement

The throw statement throws an exception to indicate an error has occured ( see "try Statements and Exceptions" on page158 in Chapter4 ):

if(w==null)

throw new ArgumentNullException(...);


Miscellaneous Statement

The using statement provides an elegant syntax for calling Dispose on objects that implement IDisposable , within a finally block ( see "try Statements and Exception" on page158 in Chapter4  and "IDisposable , Dispose , and Close" on page513 in Chapter12 ) .

- C# overloads the using keyword to have independent meanings in different contexts .

Specifically , the using directive is different from the using statement .

The lock statement is a shortcut for calling the Enter and Exit methods of the monitor class ( see Chapters 14 and 23 ) .


Namespaces

A namespace is a domain for type names .

Types are typically organized into hierarchical namespaces , making them easier to find and avoiding conflicts .

For example , the RSA type that handles public key encryption is defined within the following namespace:

System.Security.Cryptography

A namespace forms an ietegral part of a type's name .

The following code calls RSA's Create method:

System.Security.Cryptography.RSA rsa = System.Security.Cryptography.RSA.Create();

- Namespaces are independent of assemblies , which are units of deployment such as an .exe or .dll ( described in Chapter18 ) .

Namespaces also have no implact on member visibility -- public , internal , private , and so on .

The namespace keywork defines a namespace for types within that block .

For example:

namespace Outer.Middle.Inner

{

    class Class1{...}

    class Class2{...}

}

The dots in the namespace indicate a hierarchy of nested namespaces .

The code that follows is semantically identical to the preceding example:

namespace Outer

{

    namespace Middle

    {

         namespace Inner

         {

             class Calss1{...}

             class Class2{...}

         }

    }

}

You can refer to a type with its fully qualifed name , which includes all namespaces from the outermost to the innermost .

For example , we could refer to Class1 in the preceding example as Outer.Middle.Inner.Class1 .

Types not defined in any namespace are said to reside in the global namespace .

The global namespace also includes top-level namespaces , such as Outer in our example .

 

The using Directive

The using directive umports a namespace , allowing you to refer to types without their fully qualified names .

The following imports the previous example's Outer.Middle.Inner namespace:

using Outer.Middle.Inner;

 

class Test

{

    static void Main()

    {

         Class1 c;    //don't need fully qualfied name

    }

}

- It's legal ( and often desirable ) to define the same type name in different namespaces .

However , you would typically do so only if it was unlikely for a consumer to want to import both namespaces at once .

A good example , from the .NET Framework , is the TextBox class , which is defined both in System.Windows.Controls (WPF) and System.Web.UI.WebConstrols(ASP.NET) .

 

using static (C# 6)

From C# 6 , you can import not just a namespace , but a specific type , with the using static directive .

All static numbers of that type can then be used without being qualified with the type name .

In the following example , we call the Console class's static WriteLine method:

using staitc System.Console;

 

class Test

{

    static void Main(){WruteLine("hi");}

}

The using static directive imports all accessible static members of the type , including fields , properties , and nested types(Chapter3) .

You can also apply this directive to enum types(Chapter3) , in which case their members are imported .

So , if we import the following enum type:

using static System.Windows.Visibility;

we can specify Hidden instead of Visibility.Hidden:

var textBox = new TextBox{Visibility = Hidden}; // XAML-style

Should an ambiguity arise between multiple static imports , the C# compiler is not smart enough to ifer the correct type from the context , and will generate an error .

 

Rules Within Namespace

Name scoping

Names declared in outer namespaces can be used unqualified within inner namespaces .

In this example , Class1 does not need qualification within Inner:

namespace Outer

{

    class Class1{}

   

    namespace Inner

    {

         class Class2 : Class1{}

    }

}

If you want to refer to a type in a different branch of your namespace hierarchy , you can use a partially qualified name .

In the following example , we base SalesReport on Common.ReportBase:

namespace MyTradingCompany

{

    namespace Common

    {

         class ReportBse{}

    }

    namespace MangementReporting

    {

         class SalesReport : common.ReportBase{}

    }

}     

 

Name hiding

If the same type name appears in both an inner and an outer namespace , the inner name wins .

To refer to the type in the outer namespace , you must qualify its name .

For example:

namespace Outer

{

    class Foo{}

   

    namespace Inner

    {

         class Foo{}

        

         class Test

         {

             Foo f1;           // = Outer.Inner.Foo

             Outer.Foo f2; // = Outer.Foo

         }

    }

}

- All type names are converted to fully qualified names at compile time .

Intermediate Language(IL) code contains no unqualified or partially qualified names .

 

Repeated namespaces

You can repeat a namespace declaration , as long as the type names within the namespaces don't conflict:

namespace Outer.Middle.Inner

{

    class Class1{}

}

 

namespace Outer.Middle.Inner

{

    class Class2{}

}

We can even break the example into two source files such that we could compile each class into a different assembly .

Source file 1:

namespace Outer.Middle.Inner

{

    class Class1{}

}

 

Source file 2:

namespace Outer.Middle.Inner

{

    class Class2{}

}

 

Nested using directive

You can nest a using directive within a namespace .

This allows you to scope the using directive within a namespace declaration .

In the following example , Class1 is visible in one scope , but no in another:

namespace N1

{

    class Class1{}

}

namespace N2

{

    using N1;

    class Class2 : Class1{}

}

namespace N2

{

    class Class3 : Class1{}   // compile-error

}

 

Aliasing Types and Namespaces

Importing a namespace can result in type-name collision .

Rather that importing the whole namespace , you can import just the specific types you need , giving each type an alias .

For example:

using PropertyInfo2 = System.Reflection.PropertyInfo;

class Program{ PropertyInfo2 p; }

An entire namespace can be aliased , as follows:

using R = System.Reflection;

class Program{ R.PropertyInfo p; }

 

Advances Namespace Features

Extern

Extern aliases allow your program to reference two types with the same fully qualified name ( i.e. , the namespace and type name are identical ) .

This is an unusual scenario and can occur only when the two types come from different assemblies .

Consider the following example .

Libarary 1:

    // csc target:library /out:Widgets1.dll widgetsv1.cs

   

    namespace Widgets

    {

         public class Widget{}

    }

 

Libarary 2:

    // csc target:library /out:Widgets2.dll widgetsv2.cs

   

    namespace Widgets

    {

         public class Widget{}

    }

   

Application:

    // csc /r:Widgets1.dll /r:Widgets2.dll application.cs

   

    using Wedgets;

   

class Test

{

    static void Main()

    {

         Widget w = new Widget();

    }

}

The application cannot compile , because Widget is ambiguous .

Extern aliases can resolve the ambiguoty in our application:

// csc /r:W1=Widgets1.dll /r:W2=Widgets2.dll application.cs

 

extern alias W1;

extern alias W2;

 

class Test

{

    static void Main()

    {

         W1.Widgets.Widget w1 = new W1.Widgets.Widget();

         W2.Widgets.Widget w2 = new W2.Widgets.Widget();

    }

}

 

Namespace alias qualifiers

As we mentioned earlier , names in inner namespaces hide names in outer namespaces .

However , sometimes even the use of a fully qualified type name does not resolve the conflict .

Consider the following example:

namespace N

{

    class A

    {

         public class B{} //Nested type

         static void Main(){ new A.B(); }   // Instantiate class B

    }

}

 

namespace A

{

    class B{}

}

The Main method could be instantiating either the nested class B , or the class B within the namespace A .

The compiler always gives higher precedence to identifiers in the current namespace; in this case , the nested B class .

To resolve such conflicts , a namespace name can be qualified , relative to one of the following:

  • The global namespace -- the root of all namespaces (identified with the contextual keyword global )
  • The set of extern aliases

The :: token is used for namespace alias qualification .

In this example , we qualify using the global namespace ( this is most commonly seen in auto-generated code to avoid name conflicts ):

namespace N

{

    class A

    {

         static void Main()

         {

             System.Console.WriteLine(new A.B());

             System.Console.WriteLine(new global::A.B());

         }

         public class B{}

    }

}

 

namespace A

{

    class B{}

}

Here is an example of qualifying with an alias ( adapted from the example in "Extern" on page76):

extern alias W1;

extern alias W2;

 

class Test

{

    static void Main()

    {

         W1.Widgets.Widget w1 = new W1.Widgets.Widget();

         W2.Widgets.Widget w2 = new W2.Widgets.Widget();

    }

}


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 老胡泥 的頭像
    老胡泥

    Inspired from my heart © 老胡泥 🦊

    老胡泥 發表在 痞客邦 留言(0) 人氣()