Subscribe

RSS Feed (xml)

Delay Sign an Assembly in C#

Assemblies that reference strong-named assemblies contain the public key token of the referenced assemblies. This means that the referenced assembly must be strong named before it can be referenced. In a development environment in which assemblies are regularly rebuilt, this would require every developer and tester to have access to your strong name key pair—a major security risk.
Instead of distributing the private key component of your strong name key pair to all members of the development team, the .NET Framework provides a mechanism named delay signing with which you can partially strong name an assembly. The partially strong-named assembly contains the public key and the public key token (required by referencing assemblies), but contains only a placeholder for the signature that would normally be generated using the private key. 
After development is complete, the signing authority (who has responsibility for the security and use of your strong name key pair) re-signs the delay- signed assembly to complete its strong name. The signature is calculated using the private key and embedded in the assembly, making the assembly ready for distribution.
To delay sign an assembly, you need access only to the public key component of your strong name key pair. There's no security risk associated with distributing the public key, and the signing authority should make the public key freely available to all developers. 
To extract the public key component from a strong name key file named MyKeys.snk and write it to a file named MyPublicKey.snk, use the command sn -p MyKeys.snk MyPublicKey.snk. If you store your strong name key pair in a CSP key container named MyKeys, extract the public key to a file named MyPublicKey.snk using the command sn -pc MyKeys MyPublicKey.snk.
The attributes discussed are used to declare the version and culture of the assembly, as well as the location of the public key. You must also apply the attribute AssemblyDelaySign(true) to your assembly, which tells the compiler that you want to delay sign the assembly. The following code highlights the attributes you would use to delay sign the assembly, in a situation where the public key is in a file named MyPublicKey.snk.
using System;
using System.Reflection;

[assembly:AssemblyKeyFile("MyPublicKey.snk")]
[assembly:AssemblyCulture("")]
[assembly:AssemblyVersion("1.0.0.0")]
[assembly:AssemblyDelaySign(true)]

public class HelloWorld {

    public static void Main() {
    
        Console.WriteLine("Hello, world");
    }
}
When the runtime tries to load a delay-signed assembly, the runtime will identify the assembly as strong-named and will attempt to verify the assembly, as discussed. Because there's no digital signature, you must disable the runtime from verifying the assembly's strong name using the command sn -Vr HelloWorld.exe.
Once development is complete, you need to re-sign the assembly to complete the assembly's strong name. The Strong Name tool allows you to do this without the need to change your source code or to recompile the assembly; however, you must have access to the private key component of the strong name key pair. To re-sign an assembly named HelloWorld.exe with a key pair contained in the file MyKeys.snk, use the command sn -R HelloWorld.exe MyKeys.snk. If the keys are stored in a CSP key container named MyKeys, use the command sn -Rc HelloWorld.exe MyKeys.
Once you have re-signed the assembly, you should turn strong name verification for that assembly back on using the –Vu switch of the Strong Name tool, as in sn -Vu HelloWorld.exe. To enable verification for all assemblies for which you have disabled strong name verification, use the command sn -Vx. You can list the assemblies for which verification is disabled using the command sn -Vl.

Verify That a Strong-Named Assembly Has Not Been Modified in C#

Whenever the .NET runtime loads a strong-named assembly, the runtime extracts the encrypted hash code that's embedded in the assembly and decrypts it with the public key, which is also embedded in the assembly. The runtime then calculates the hash code of the assembly manifest and compares it to the decrypted hash code. This verification process will identify if the assembly has changed after compilation.
If an executable assembly fails strong name verification, the runtime will display the dialog box shown in Figure 1.2. If code tries to load an assembly that fails verification, the runtime will throw a System.IO.FileLoadException with the message "Strong name validation failed."
Figure 1.2: Error shown when you try to execute a strong-named assembly that has been modified.
As well as the generation and management of strong name keys, the Strong Name tool allows you to verify strong-named assemblies. To verify that the strong-named assembly HelloWorld.exe is unchanged, use the command sn -vf HelloWorld.exe. The -v switch requests the Strong Name tool to verify the strong name of the specified assembly, and the -f switch forces strong name verification even if it has been previously disabled for the specified assembly. (You can disable strong name verification for specific assemblies using the -Vr switch, as in sn -Vr HelloWorld.exe;
If the assembly passes strong name verification, you will see the following output:
Microsoft (R) .NET Framework Strong Name Utility  Version 1.1.4322.573Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.Assembly 'HelloWorld.exe' is valid
However, if the assembly has been modified, you will see the message
Microsoft (R) .NET Framework Strong Name Utility  Version 1.1.4322.573Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.Failed to verify assembly -- Unable to format error message 8013141A

Give an Assembly a Strong Name in C#

To strong name an assembly using the C# compiler, you need the following:
  • A strong name key pair contained either in a file or in a CSP key container.
  • To use assembly-level attributes to specify the location where the compiler can obtain your strong name key pair.
    • If your key pair is in a file, apply the attribute System.Reflection.AssemblyKeyFileAttribute to your assembly and specify the name of the file that contains the keys.
    • If your key pair is in a CSP container, apply the attribute System.Reflection.AssemblyKeyNameAttribute to your assembly and specify the name of the container in which the keys are stored.
Optionally, you can also
  • Specify the culture that your assembly supports by applying the attribute System.Reflection.AssemblyCultureAttribute to the assembly. (You can't specify a culture for executable assemblies because executable assemblies support only the neutral culture.)
  • Specify the version of your assembly by applying the attribute System.Reflection.AssemblyVersionAttribute to the assembly.
The code that follows (from a file named HelloWorld.cs) shows the use of attributes (shown in boldface text) to specify the keys, the culture, and the version for the assembly.
using System;using System.Reflection;[assembly:AssemblyKeyName("MyKeys")][assembly:AssemblyCulture("")][assembly:AssemblyVersion("1.0.0.0")]public class HelloWorld {public static void Main() {Console.WriteLine("Hello, world");}}
To create a strong-named assembly from the example code, create the strong name keys and store them in a file named MyKeyFile using the command sn -k MyKeyFile.snk. Then install the keys into the CSP container named MyKeys using the command sn -i MyKeyFile.snk MyKeys. You can now compile the HelloWorld.cs file into a strong-named assembly using the command csc HelloWorld.cs.
Note:
You can also build strong-named assemblies using the Assembly Linker (al.exe), which allows you to specify the strong name information on the command line instead of using attributes in your code. This is useful when you don't want to embed the strong name attributes in your source file and when you use scripts to build large source trees. Refer to the Assembly Linker information in the .NET Framework SDK documentation for more details.

Create and Manage Strong-Named Key Pairs in C#

To generate a new key pair and store them in the file named MyKeys.snk, execute the command sn -k MyKeys.snk. (.snk is the usual extension given to files containing strong name keys.) The generated file contains both your public and private keys. You can view the public key using the command sn -tp MyKeys.snk, which will generate output similar to the (abbreviated) listing shown here.
Microsoft (R) .NET Framework Strong Name Utility  Version 1.1.4322.573Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.Public key is07020000002400005253413200040000010001002b4ef3c2bbd6478802b64d0dd3f2e7c65ee;<$VE>6478802b63cb894a782f3a1adbb46d3ee5ec5577e7dccc818937e964cbe997c12076c19f2d7ad179f15f7dccca6c6b72aPublic key token is 2a1d3326445fc02a
The public key token shown at the end of the listing is the last 8 bytes of a cryptographic hash code computed from the public key. Because the public key is so long, .NET uses the public key token for display purposes and as a compact mechanism for other assemblies to reference your public key.
As the name suggests, you don't need to keep the public key (or public key token) secret. When you strong name your assembly , the compiler uses your private key to generate a digital signature (an encrypted hash code) of the assembly's manifest. The compiler embeds the digital signature and your public key in the assembly so that any consumer of the assembly can verify the digital signature.
Keeping your private key secret is imperative. People with access to your private key can alter your assembly and create a new strong name—leaving your customers unaware that they are using modified code. There's no mechanism to repudiate compromised strong name keys. If your private key is compromised, you must generate new keys and distribute new versions of your assemblies that are strong named using the new keys. You must also notify your customers about the compromised keys and explain to them which versions of your public key to trust—in all, a very costly exercise in terms of both money and credibility. There are many ways to protect your private key; the approach you use will depend on factors such as
  • The structure and size of your organization.
  • Your development and release process.
  • The software and hardware resources you have available.
  • The requirements of your customer base.
    Tip
    Commonly, a small group of trusted individuals (the signing authority) has responsibility for the security of your company's strong name signing keys and is responsible for signing all assemblies just prior to their final release. The ability to delay sign an assembly facilitates this model and avoids the need to distribute private keys to all development team members.
One feature provided by the Strong Name tool to simplify the security of strong name keys is the use of CSP key containers. Once you have generated a key pair to a file, you can install the keys into a key container and delete the file. For example, to store the key pair contained in the file MyKeys.snk to a CSP container named StrongNameKeys, use the command sn -i MyKeys.snk StrongNameKeys.
An important aspect of CSP key containers is the fact that there are user- based containers and machine-based containers. Windows security ensures each user can access only their own user-based key containers. However, any user of a machine can access a machine-based container.
By default, the Strong Name tool uses machine-based key containers, meaning that anybody who can log on to your machine and who knows the name of your key container can sign an assembly with your strong name keys. To change the Strong Name tool to use user-based containers, use the command sn -m n, and to change back to machine-based stores, use the command sn -m y. The command sn -m will display whether the Strong Name tool is currently configured to use machine-based or user-based containers.
To delete the strong name keys from the StrongNameKeys container (as well as the container), use the command sn -d StrongNameKeys.

Access a Program Element That Has the Same Name as a Keyword in C#

The .Net Framework allows you to use software components developed in other .NET languages from within your C# applications. Each language has its own set of keywords (or reserved words) and imposes different restrictions on the names that programmers can assign to program elements such as types, members, and variables. Therefore, it's possible that a programmer developing a component in another language will inadvertently use a C# keyword as the name of a program element. The symbol @ enables you to use a C# keyword as an identifier and overcome these possible naming conflicts. This code fragment instantiates an object of type operator (perhaps a telephone operator) and sets its volatile property to true—both operator and volatile are C# key words.
// Instantiate an operator object@operator Operator1 = new @operator();// Set the operator's volatile propertyOperator1.@volatile = true;

Selectively Include Code at Build Time in C#

If you need your application to function differently depending on factors such as the platform or environment on which it runs, you can build run-time checks into the logic of your code that trigger the variations in operation. However, such an approach can bloat your code and affect performance, especially if there are many variations to support or many locations where evaluations need to be made. An alternative approach is to build multiple versions of your application to support the different target platforms and environments. Although this approach overcomes the problems of code bloat and performance degradation, it would be an untenable solution if you had to maintain different source code for each version, so C# provides features that allow you to build customized versions of your application from a single code base.
The #if, #elif, #else, and #endif preprocessor directives allow you to identify blocks of code that the compiler should include in your assembly only if specified symbols are defined at compile time. Symbols function as on/off switches; they don't have values—either the symbol is defined, or it is not. To define a symbol you can use either the #define directive in your code or use the /define compiler switch. Symbols defined using #define are active until the end of the file in which they are defined. Symbols defined using the /define compiler switch are active in all source files that are being compiled. To undefine a symbol defined using the /define compiler switch, C# provides the #undef directive, which is useful if you want to ensure a symbol is not defined in specific source files. All #define and #undef directives must appear at the top of your source file before any code, including any using directives. Symbols are case sensitive.
In this example, the code assigns a different value to the local variable platformName based on whether the winXP, win2000, winNT, or Win98 symbols are defined. The head of the code defines the symbols win2000 and release (not used in this example) and undefines the win98 symbol in case it was defined on the compiler command line.
#define win2000#define release#undef  win98using System;public class ConditionalExample {public static void Main() {// Declare a string to contain the platform namestring platformName;#if winXP       // Compiling for Windows XPplatformName = "Microsoft Windows XP";#elif win2000   // Compiling for Windows 2000platformName = "Microsoft Windows 2000";#elif winNT     // Compiling for Windows NTplatformName = "Microsoft Windows NT";#elif win98     // Compiling for Windows 98platformName = "Microsoft Windows 98";#else           // Unknown platform specifiedplatformName = "Unknown";#endifConsole.WriteLine(platformName);}}
To build the ConditionalExample class (contained in a file named ConditionalExample.cs) and define the symbols winXP and DEBUG (not used in this example), use the command csc /define:winXP;DEBUG ConditionalExample.cs.
The #if .. #endif construct evaluates #if and #elif clauses only until it finds one that evaluates to true, meaning that if you define multiple symbols (winXP and win2000, for example), the order of your clauses is important. The compiler includes only the code in the clause that evaluates to true. If no clause evaluates to true, the compiler includes the code in the #else clause.
You can also use logical operators to base conditional compilation on more than one symbol. Table 1.1 summarizes the supported operators.
Table 1.1: Logical Operators Supported by the #if..#endif Directive
Operator
Example
Description
==
#if winXP == true
Equality. Evaluates to true if the symbol winXP is defined. Equivalent to #if winXP.
!=
#if winXP != true
Inequality. Evaluates to true if the symbol winXP is not defined. Equivalent to #if !winXP.
&&
#if winXP && release
Logical AND. Evaluates to true only if the symbols winXP AND release are defined.
||
#if winXP || release
Logical OR. Evaluates to true if either of the symbols winXP OR release are defined.
()
#if (winXP || win2000) && release
Parentheses allow you to group expressions. Evaluates to true if the symbols winXP OR win2000 are defined AND the symbol release is defined.
Warning
You must be careful not to overuse conditional compilation directives and not to make your conditional expressions too complex; otherwise, your code can quickly become confusing and unmanageable—especially as your projects become larger.
A less flexible but more elegant alternative to the #if preprocessor directive is the attribute System.Diagnostics.ConditionalAttribute. If you apply ConditionalAttribute to a method, the compiler will ignore any calls to the method if the symbol specified by ConditionalAttribute is not defined at the calling point. In the following code, ConditionalAttribute specifies that calls to the DumpState method should be included in a compiled assembly only if the symbol DEBUG is defined during compilation.
[System.Diagnostics.Conditional("DEBUG")]public static void DumpState() {//…}
Use of ConditionalAttribute centralizes your conditional compilation logic on the method declaration and means that you can freely include calls to conditional methods without the need to litter your code with #if directives. However, because the compiler literally removes calls to the conditional method from your code, your code can't have dependencies on return values from the conditional method. This means that you can apply ConditionalAttribute only to methods that return void.
You can apply multiple ConditionalAttribute instances to a method in order to produce logical OR behavior. Calls to the following version of the DumpState method will be compiled only if the DEBUG OR TEST symbols are defined.
[System.Diagnostics.Conditional("DEBUG")][System.Diagnostics.Conditional("TEST")]public static void DumpState() {//…}
Achieving logical AND behavior is not as clean and involves the use of an intermediate conditional method, quickly leading to overly complex code that is hard to understand and maintain. Here is a quick example that requires definition of both the DEBUG AND TEST symbols for the DumpState functionality (contained in DumpState2) to be called.
[System.Diagnostics.Conditional("DEBUG")]public static void DumpState() {DumpState2();}[System.Diagnostics.Conditional("TEST")]public static void DumpState2() {//…}
Note
The Debug and Trace classes from the System.Diagnostics namespace use ConditionalAttribute on many of their methods. The methods of the Debug class are conditional on the definition of the symbol DEBUG, and the methods of the Trace class are conditional on the definition of the symbol TRACE.

Access Command-Line Arguments in C#

Declaring your application's Main method with one of the following signatures provides access to the command-line arguments as a string array.
public static void Main(string[] args) {}public static int Main(string[] args) {}
At run time, the args argument will contain a string for each value entered on the command line after your application's name. To demonstrate this, the Main method in the following example steps through each of the command- line arguments passed to it and displays them to the console.
public class CmdLineArgExample {public static void Main(string[] args) {// Step through the command-line argumentsforeach (string s in args) {       System.Console.WriteLine(s);}}}
It's important to understand how the arguments are passed to the application. If you execute the CmdLineArgExample using the following command:
CmdLineArgExample "one \"two\"    three" four 'five    six'
the application will generate the following output on the console:
one "two"    threefour'fivesix'
Notice that unlike C and C++, the application's name is not included in the array of arguments. Also notice that the use of double quotes (") results in more than one word being treated as a single argument, although single quotes (') do not. You can include double quotes in an argument by escaping them with the backslash character (\). Finally, notice that all spaces are stripped from the command line unless they are enclosed in double quotes.
If you need access to the command-line arguments at places in your code other than the Main method, you can process the command-line arguments in your Main method and store them for later access. Alternatively, you can use the System.Environment class, which provides two static members that return information about the command line: CommandLine and GetCommandLineArgs. The CommandLine property returns a string containing the full command line that launched the current process. Depending on the operating system on which the application is running, path information might precede the application name. Microsoft Windows NT 4.0, Windows 2000, and Windows XP don't include path information, whereas Windows 98 and Windows ME do. The GetCommandLineArgs method returns a string array containing the command-line arguments. This array can be processed in the same way as the string array passed to the Main method, as discussed at the start of this section. Unlike the array passed to the Main method, the first element in the array returned by the GetCommandLineArgs method is the name of the application.

Create and Use a Code Library in C#

You were to extend the functionality of the ConsoleUtils class, it could contain functionality useful to many applications. Instead of including the source code for ConsoleUtils in every application, you can build it into a library and deploy it independently, making the functionality accessible to many applications.
To build the ConsoleUtils.cs file into a library, use the command csc /target:library ConsoleUtils.cs. This will produce a library file named ConsoleUtils.dll. To build a library from multiple source files, list the name of each file at the end of the command. You can also specify the name of the library using the /out compiler switch; otherwise, the library is named after the first source file listed. For example, to build a library named MyFirstLibrary.dll from two source files named ConsoleUtils.cs and WindowsUtils.cs, use the command csc /out:MyFirstLibrary.dll /target:library ConsoleUtils.cs WindowsUtils.cs.
Before distributing your library, you might consider strong naming it so that nobody can modify your assembly and pass it off as being the original. Strong naming your library also allows people to install it into the global assembly cache, which makes reuse much easier. (Recipe 1.9 describes how to strong name your assembly and recipe 1.14 describes how to install a strong-named assembly into the global assembly cache.) You might also consider signing your library with an Authenticode signature, which allows users to confirm that you are the publisher of the assembly—see recipe 1.12 for details on signing assemblies with Authenticode.
To compile an assembly that relies on types declared within external libraries, you must tell the compiler which libraries are referenced using the /reference compiler switch. For example, to compile the HelloWorld.cs source file (from recipe 1.1) if the ConsoleUtils class is contained in the ConsoleUtils.dll library, use the command csc /reference:ConsoleUtils.dll HelloWorld.cs. Three points worth remembering are
  • If you reference more than one library, separate each library name with a comma or semicolon, but no spaces. For example, /reference:ConsoleUtils.dll,WindowsUtils.dll.
  • If the libraries aren't in the same directory as the source code, use the /lib switch on the compiler to specify the additional directories where the compiler should look for libraries. For example, /lib:c:\CommonLibraries,c:\Dev\ThirdPartyLibs.
  • If the library you need to reference is a multi-file assembly, reference the file that contains the assembly manifest. (For information about multi-file assemblies, see recipe 1.3.)

Create and Use a Code Module in C#

Modules are the building blocks of .NET assemblies. Modules consist of a single file that contains the following:
  • Microsoft Intermediate Language (MSIL) code created from your C# source code during compilation
  • Metadata describing the types contained in the module
  • Resources, such as icons and string tables, used by the types in the module
Assemblies consist of one or more modules and an assembly manifest. When there is a single module, the module and assembly manifest are usually built into a single file for convenience. When there is more than one module, the assembly represents a logical grouping of more than one file that you must deploy as a complete unit. In these situations, the assembly manifest is either contained in a separate file or built into one of the modules.
By building an assembly from multiple modules, you complicate the management and deployment of the assembly, but under some circumstances, modules offer significant benefits, including:
  • The runtime will load a module only when the types defined in the module are required. Therefore, where you have a set of types that your application uses rarely, you can partition them into a separate module that the runtime will load only if necessary. This offers the following benefits:
    • Improved performance, especially if your application is loaded across a network.
    • Minimizing the use of memory.
  • The ability to use many different languages to write applications that run on the common language runtime (CLR) is a great strength of the .NET Framework. However, the C# compiler can't compile your Microsoft Visual Basic .NET or COBOL .NET code for inclusion in your assembly. You must first use a language-specific compiler to turn your source into MSIL in a structure that the C# compiler can incorporate—a module. Likewise, if you want to allow programmers of other languages to use the types you develop in C#, you must build them into a module.
To compile a source file named ConsoleUtils.cs into a module use the command csc /target:module ConsoleUtils.cs. The result is the creation of a file named ConsoleUtils.netmodule. The netmodule extension is the default extension for modules, and the file name is the same as the name of the C# source file.
You can also build modules from multiple source files, which results in a single file (module) containing the MSIL and metadata for all types contained in all the source files. The command csc /target:module ConsoleUtils.cs WindowsUtils.cs compiles two source files named ConsoleUtils.cs and WindowsUtils.cs to create the module named ConsoleUtils.netmodule. The module is named after the first source file listed unless you override the name with the /out compiler switch. For example, the command csc /target:module /out:Utilities.netmodule ConsoleUtils.cs WindowsUtils.cs creates a module named Utilities.netmodule.
To build an assembly consisting of multiple modules, you must use the /addmodule compiler switch. To build an executable named MyFirstApp.exe from two modules named WindowsUtils.netmodule and ConsoleUtils.netmodule and two source files named SourceOne.cs and SourceTwo.cs, use the command csc /out:MyFirstApp.exe /target:exe /addmodule:WindowsUtils.netmodule,ConsoleUtils.netmodule SourceOne.cs SourceTwo.cs. This command will result in an assembly consisting of the following files:
  • MyFirstApp.exe, which contains the assembly manifest as well as the MSIL for the types declared in the SourceOne.cs and SourceTwo source files.
  • ConsoleUtils.netmodule and WindowsUtils.netmodule, which are now integral components of the multi-file assembly but are unchanged by this compilation process. (If you attempt to run MyFirstApp.exe without the netmodules present, a System.IO.FileNotFoundException is thrown.)

Short-Circuit Logical Operators in C#

C# supplies special short-circuit versions of its AND and OR logical operators that can be used to produce more efficient code. To understand why, consider the following: In an AND operation, if the first operand is false, the outcome is false no matter what value the second operand has. In an OR operation, if the first operand is true, the outcome of the operation is true no matter what the value of the second operand. Thus, in these two cases there is no need to evaluate the second operand. By not evaluating the second operand, time is saved and more efficient code is produced.
The short-circuit AND operator is && and the short-circuit OR operator is | |. As described earlier, their normal counterparts are & and |. The only difference between the normal and short-circuit versions is that the normal operands will always evaluate each operand, but short-circuit versions will evaluate the second operand only when necessary.
Here is a program that demonstrates the short-circuit AND operator. The program determines if the value in d is a factor of n. It does this by performing a modulus operation. If the remainder of n / d is zero, then d is a factor. However, since the modulus operation involves a division, the short-circuit form of the AND is used to prevent a divide-by-zero error.
// Demonstrate the short-circuit operators,

using System;

class SCops {
  public static void Main() {
    int n, d;
    
    n = 10;
    d = 2;
    if(d != 0 && (n % d) == 0)
      Console.WriteLine(d + " is a factor of " + n);
      
    d = 0; // now, set d to zero
    
    // Since d is zero, the second operand is not evaluated,
    if(d != 0 && (n % d) == 0)
      Console.WriteLine(d + " is a factor of " + n);
      
    /* Now, try the same thing without short-circuit operator.
       This will cause a divide-by-zero error. */
    if(d != 0 & (n % d) == 0)
      Console.WriteLine(d + " is a factor of " + n);
  }
} 
To prevent a divide-by-zero error, the if statement first checks to see if d is equal to zero. If it is, the short-circuit AND stops at that point and does not perform the modulus division. Thus, in the first test, d is 2 and the modulus operation is performed. The second test fails because d is set to zero, and the modulus operation is skipped, avoiding a divide-by-zero error. Finally, the normal AND operator is tried. This causes both operands to be evaluated, which leads to a runtime error when the division-by-zero occurs.
Since the short-circuit operators are, in some cases, more efficient than their normal counterparts, you might be wondering why C# still offers the normal AND and OR operators. The answer is that in some cases you will want both operands of an AND or OR operation to be evaluated because of the side effects produced. Consider the following:
// Side effects can be important,

using System;

class SideEffects {
  public static void Main() {
    int i;
    bool someCondition = false;

    i = 0;

    /* Here, i is still incremented even though
       the if statement fails. */
    if(someCondition & (++i < 100))
       Console.WriteLine("this won't be displayed");
    Console.WriteLine("if statement executed: " + i); // displays 1

    /* In this case, i is not incremented because
       the short-circuit operator skips the increment. */
    if(someCondition && (++i < 100))
      Console.WriteLine("this won't be displayed");
    Console.WriteLine("if statement executed: " + i); // still 1 !!
  }
}
First, notice that the bool variable someCondition is initialized to false. Next, examine each if statement. As the comments indicate, in the first if statement, i is incremented despite the fact that someCondition is false. When the & is used, as it is in the first if, the expression on the right side of the & is evaluated no matter what value the expression on the left has. However, in the second if statement, the short-circuit operator is used. In this case, the variable i is not incremented because the left operand, someCondition, is false, which causes the expression on the right to be skipped. The lesson here is that if your code expects the right-hand operand of an AND or OR operation to be evaluated, then you must use C#'s non-short-circuit forms of these operations.

Relational and Logical Operators in C#

In the terms relational operator and logical operator, relational refers to the relationships that values can have with one another, and logical refers to the ways in which true and false values can be connected together. Since the relational operators produce true or false results, they often work with the logical operators. For this reason they will be discussed together here.
The relational operators are as follows:
Operator
Meaning
= =
Equal to
!=
Not equal to
>
Greater than
<
Less than
>=
Greater than or equal to
<=
Less than or equal to
The logical operators are shown next:
Operator
Meaning
&
AND
|
OR
^
XOR (exclusive OR)
||
Short-circuit OR
&&
Short-circuit AND
!
NOT
The outcome of the relational and logical operators is a bool value.
In C#, all objects can be compared for equality or inequality using = = and !=. However, the comparison operators, <, >, <=, or >=, can be applied only to those types that support an ordering relationship. Therefore, all of the relational operators can be applied to all numeric types. However, values of type bool can only be compared for equality or inequality, since the true and false values are not ordered. For example, true > false has no meaning in C#.
For the logical operators, the operands must be of type bool, and the result of a logical operation is of type bool. The logical operators, &, |, ^, and !, support the basic logical operations AND, OR, XOR, and NOT, according to the following truth table:
p
q
p&q
p|q
p^q
!p
False
False
False
False
False
True
True
False
False
True
True
False
False
True
False
True
True
True
True
True
True
True
False
False
As the table shows, the outcome of an exclusive OR operation is true when exactly one and only one operand is true.
Here is a program that demonstrates several of the relational and logical operators:
// Demonstrate the relational and logical operators,

using System;

class RelLogOps {
  public static void Main() {
    int i, j;
    bool b1, b2;

    i = 10;
    j = 11;
    if(i < j) Console.WriteLine("i < j");
    if(i <= j) Console.WriteLine("i <= j");
    if(i != j) Console.WriteLine("i != j");
    if(i == j) Console.WriteLine("this won't execute");
    if(i >= j) Console.WriteLine("this won't execute");
    if(i > j) Console.WriteLine("this won't execute");

    b1 = true;
    b2 = false;
    if(b1 & b2) Console.WriteLine("this won't execute");
    if(!(b1 & b2)) Console.WriteLine("!(b1 & b2) is true");
    if(b1 b2) Console.WriteLine("b1 | b2 is true");
    if(b1 ^ b2) Console.WriteLine("b1 ^ b2 is true");
  } 
}
The output from the program is shown here:
i < j
i <= j
i != j
! (b1 & b2) is true
b1 | b2 is true
b1 ^ b2 is true   
The logical operators provided by C# perform the most commonly used logical operations. However, there are several other operations defined by the rules for formal logic. These other logical operations can be constructed using the logical operators supported by C#. Thus, C# supplies a set of logical operators sufficient to construct any other logical operation. For example, another logical operation is implication. Implication is a binary operation in which the outcome is false only when the left operand is true and the right operand is false. (The implication operation reflects the idea that true cannot imply false.) Thus, the truth table for the implication operator is shown here:
p
q
p implies q
True
True
True
True
False
False
False
False
True
False
True
True
The implication operation can be constructed using a combination of the ! and the I operator, as shown here:
!p | q
The following program demonstrates this implementation:
// Create an implication operator in C#.

using System;

class Implication {
  public static void Main() {
    bool p=false, q=false;
    int i, j;

    for(i =0; i < 2; i++) {
      for(j = 0; j < 2; j++) {
        if (i==0) p = true;
        if (i==1) p = false;
        if (j==0) q = true;
        if (j==1) q = false;

        Console .WriteLine ("p is " + p + '' q is " + q);
        if(!p | q) Console.WriteLine(p + implies " + q +
                    " is " + true);
        Console.WriteLine();
      }
    }
  }
}

The output is shown here:
p is True, q is True
True implies True is True

p is True, q is False

p is False, q is True
False implies True is True

p is False, q is False
False implies False is True

Arithmetic Operators in C#

C# defines the following arithmetic operators:
Operator
Meaning
+
; Addition
Subtraction (also unary minus)
*
Multiplication
/
Division
%
Modulus
++
Increment
−−
Decrement
The operators +, −, *, and / all work the same way in C# as they do in any other computer language (or in algebra, for that matter). These can be applied to any built-in numeric data type.
Although the actions of arithmetic operators are well known to all readers, a few special situations warrant some explanation. First, remember that when / is applied to an integer, any remainder will be truncated; for example, 10/3 will equal 3 in integer division. You can obtain the remainder of this division by using the modulus operator, %. The % is also referred to as the remainder operator. It yields the remainder of an integer division. For example, 10 % 3 is 1. In C#, the % can be applied to both integer and floating-point types.
Thus, 10.0 % 3.0 is also 1. (This differs from C/C++, which allow modulus operations only on integer types.) The following program demonstrates the modulus operator:
// Demonstrate the % operator,

using System;

class ModDemo {
  public static void Main() {
    int iresult, irem;
    double dresult, drem;

    iresult = 10 / 3;
    irem = 10 % 3;

    dresult = 10.0 / 3.0;
    drem = 10.0 % 3.0;

    Console.WriteLine("Result and remainder of 10 / 3: " +
                       iresult + " " + irem);
    Console.WriteLine("Result and remainder of 10.0 / 3.0: " +
                       dresult + " " + drem);
  }
}
The output from the program is shown here:
Result and remainder of 10 / 3: 3 1
Result and remainder of 10.0 / 3.0: 3.33333333333333 1
As you can see, the % yields a remainder of 1 for both integer and floating-point operations.

Increment and Decrement

The ++ and the −− are the increment and decrement operators. As you will see, they have some special properties that make them quite interesting. Let’s begin by reviewing precisely what the increment and decrement operators do.
The increment operator adds 1 to its operand, and the decrement operator subtracts 1. Therefore,
x = x + 1;
is the same as
x++;
and
x = x − 1;
is the same as
x−−;   
Both the increment and decrement operators can either precede (prefix) or follow (postfix) the operand. For example:
x = x + 1;
can be written as
++x; // prefix form
or as
x++; // postfix form
In the foregoing example, there is no difference whether the increment is applied as a prefix or a postfix. However, when an increment or decrement is used as part of a larger expression, there is an important difference. When an increment or decrement operator precedes its operand, C# will perform the operation prior to obtaining the operand’s value for use by the rest of the expression. If the opera tor follows its operand, then C# will obtain the operand's value before incrementing or decrementing it. Consider the following:
x = 10;
y = ++x;
In this case, y will be set to 11. However, if the code is written as
x = 10;
y = x++;
then y will be set to 10. In both cases, x is still set to 11; the difference is when it happens.
There are significant advantages in being able to control when the increment or decrement operation takes place. Consider the following program, which generates a series of numbers:
/*
   Demonstrate the difference between prefix
   postfix forms of ++.
*/
using System;

class PrePostDemo {
  public static void Main() {
    int x, y;
    int i;

    x = 1;
    Console.WriteLine("Series generated using y = x + x++;");
    for(i = 0; i < 10; i++) {

      y = x + x++; // postfix ++

      Console.WriteLine(y + " ");
    }
    Console.WriteLine();
    x = 1;
    Console.WriteLine("Series generated using y = x + ++x;");
    for(i = 0; i < 10; i++) {

      y = x + ++x; // prefix ++

      Console.WriteLine(y + " ");
    }
    Console.WriteLine();

  }
}
The output is shown here:
Series generated using y = x + x++;
2
4
6
8
10
12
14
16
18
20

Series generated using y = x + ++x;
3
5
7
9
11
13
15
17
19
21
As the output confirms, the statement
y = x + x++;
adds the value of x to x and assigns that result to y. It then increments x. However, the statement
y = x + ++x;
obtains the value of x, then increments x, and then adds that value to the original value of x. The result is assigned to y. As the output shows, simply changing x++ to ++x changes the number series from even to odd.
One other point about the preceding example: Don't let expressions like
x + ++x
intimidate you. Although having two operators back-to-back is a bit unsettling at first glance, the compiler keeps it all straight. Just remember, this expression simply adds the value of x to the value of x incremented.

Type Conversion of Expressions in C#

In addition to occurring within an assignment, type conversions also take place within an expression. In an expression, you can freely mix two or more different types of data as long as they are compatible with each other. For example, you can mix short and long within an expression because they are both numeric types. When different types of data are mixed within an expression, they are converted to the same type, on an operation-by-operation basis.
The conversions are accomplished through the use of C#’s type promotion rules. Here is the algorithm that they define for binary operations:

  • IF one operand is a decimal, THEN the other operand is promoted to decimal (unless it is of type float or double, in which case an error results).

  • ELSE IF one operand is a double, the second is promoted to double.

  • ELSE IF one operand is a float, the second is promoted to float.

  • ELSE IF one operand is a ulong, the second is promoted to ulong (unless it is of type sbyte, short, int, or long, in which case an error results).

  • ELSE IF one operand is a long, the second is promoted to long.

  • ELSE IF one operand is a uint and the second is of type sbyte, short, or int, both are promoted to long.

  • ELSE IF one operand is a uint, the second is promoted to uint.

  • ELSE both operands are promoted to int.
There are a couple of important points to be made about the type promotion rules. First, not all types can be mixed in an expression. Specifically, there is no implicit conversion from float or double to decimal, and it is not possible to mix ulong with any signed integer type. To mix these types requires the use of an explicit cast.
Second, pay special attention to the last rule. It states that if none of the preceding rules applies, then all other operands are promoted to int. Therefore, in an expression, all char, sbyte, byte, ushort, and short values are promoted to int for the purposes of calculation. This is called integer promotion. It also means that the outcome of all arithmetic operations will be no smaller than int.
It is important to understand that type promotions only apply to the values operated upon when an expression is evaluated. For example, if the value of a byte variable is promoted to int inside an expression, outside the expression, the variable is still a byte. Type promotion only affects the evaluation of an expression.
Type promotion can, however, lead to somewhat unexpected results. For example, when an arithmetic operation involves two byte values, the following sequence occurs. First, the byte operands are promoted to int. Then the operation takes place, yielding an int result. Thus, the outcome of an operation involving two byte values will be an int. This is not what you might intuitively expect. Consider the following program:
// A promotion surprise!

using System;

class PromDemo {
public static void Main() {
byte b;

b = 10;
b = (byte) (b * b); // cast needed!!

Console.WriteLine("b: "+ b);
}
}
Somewhat counterintuitively, a cast to byte is needed when assigning b * b back to b! The reason is because in b * b, the value of b is promoted to int when the expression is evaluated. Thus, b * b results in an int value, which cannot be assigned to a byte variable without a cast. Keep this in mind if you get unexpected type-incompatibility error messages on expressions that would otherwise seem perfectly OK.
This same sort of situation also occurs when performing operations on chars. For example, in the following fragment, the cast back to char is needed because of the promotion of ch1 and ch2 to int within the expression
char ch1 = 'a', ch2 = 'b';

ch1 = (char) (ch1 + ch2);
Without the cast, the result of adding ch1 to ch2 would be int, which can’t be assigned to a char.
Type promotions also occur when a unary operation, such as the unary , takes place. For the unary operations, operands smaller than int (byte, sbyte, short, and ushort) are promoted to int. Also, a char operand is converted to int. Furthermore, if a uint value is negated, it is promoted to long.

Using Casts in Expressions

A cast can be applied to a specific portion of a larger expression. This gives you fine-grained control over the way type conversions occur when an expression is evaluated. For example, consider the following program. It displays the square roots of the numbers from 1 to 10. It also displays the whole number portion and the fractional part of each result, separately. To do so, it uses a cast to convert the result of Math.Sqrt( ) to int.
// Using casts in an expression.

using System;

class CastExpr {
public static void Main() {
double n;

for(n = 1.0; n <= 10; n++) {       Console.WriteLine("The square root of {0} is {1}",                         n, Math.Sqrt(n));        Console.WriteLine("Whole number part: {0}" ,                         (int) Math.Sqrt(n));        Console.WriteLine("Fractional part: {0}",                         Math.Sqrt(n) - (int) Math.Sqrt(n) );       Console.WriteLine();    }   } }
Here is the output from the program:
The square root of 1 is 1
Whole number part: 1
Fractional part: 0
The square root of 2 is 1.4142135623731
Whole number part: 1
Fractional part: 0.414213562373095

The square root of 3 is 1.73205080756888
Whole number part: 1
Fractional part: 0.732050807568877

The square root of 4 is 2
Whole number part: 2
Fractional part: 0

The square root of 5 is 2.23606797749979
Whole number part: 2
Fractional part: 0.23606797749979

The square root of 6 is 2.44948974278318
Whole number part: 2
Fractional part: 0.449489742783178

The square root of 7 is 2.64575131106459
Whole number part: 2
Fractional part: 0.645751311064591

The square root of 8 is 2.82842712474619
Whole number part: 2
Fractional part: 0.82842712474619

The square root of 9 is 3
Whole number part: 3
Fractional part: 0

The square root of 10 is 3.16227766016838
Whole number part: 3
Fractional part: 0.16227766016838
As the output shows, the cast of Math.Sqrt( ) to int results in the whole number component of the value. In this expression:
Math.Sqrt(n) - (int) Math.Sqrt(n)
the cast to int obtains the whole number component, which is then subtracted from the complete value, yielding the fractional component. Thus, the outcome of the expression is double. Only the value of the second call to Math.Sqrt( ) is cast to int. (The slight discrepancies in the fractional parts are due to rounding errors.)

C# Type Conversion and Casting

In programming, it is common to assign one type of variable to another. For example, you might want to assign an int value to a float variable, as shown here:

int i;
float f;

i = 10;
f = i; // assign an int to a float

When compatible types are mixed in an assignment, the value of the right side is automatically converted to the type of the left side. Thus, in the preceding fragment, the value in i is converted into a float and then assigned to f. However, because of C#’s strict type-checking, not all types are compatible, and thus, not all type conversions are implicitly allowed. For example, bool and int are not compatible. Fortunately, it is still possible to obtain a conversion between incompatible types by using a cast. A cast performs an explicit type conversion. Both automatic type conversion and casting are examined here.

Automatic Conversions

When one type of data is assigned to another type of variable, an automatic type conversion will take place if

  • The two types are compatible.

  • The destination type has a range that is greater than the source type.

When these two conditions are met, a widening conversion takes place. For example, the int type is always large enough to hold all valid byte values, and both int and byte are integer types, so an automatic conversion can be applied. An automatic type conversion is also called an implicit conversion.

For widening conversions, the numeric types, including integer and floating-point types, are compatible with each other. For example, the following program is perfectly valid since long to double is a widening conversion that is automatically performed.

// Demonstrate automatic conversion from long to double.

using System;

class LtoD {
 public static void Main() {
   long L;
   double D;

   L = 100123285L;
   D = L;

   Console.WriteLine("L and D: " + L + " " + D);
 }
}

Although there is an automatic conversion from long to double, there is no automatic conversion from double to long since this is not a widening conversion. Thus, the following version of the preceding program is invalid:

// *** This program will not compile. ***

using System;

class LtoD {
 public static void Main() {
   long L;
   double D;

   D = 100123285.0;
   L = D; // Illegal!!!

   Console.WriteLine("L and D: " + L + " " + D);

 }
}

In addition to the restrictions just described, there are no automatic conversions between decimal and float or double, or from the numeric types to char or bool. Also, char and bool are not compatible with each other.

Casting Incompatible Types

Although the automatic type conversions are helpful, they will not fulfill all programming needs, because they apply only to widening conversions between compatible types. For all other cases, you must employ a cast. A cast is an instruction to the compiler to convert one type into another. Thus, it requests an explicit type conversion. A cast has this general form:

(target-type) expression 

Here, target-type specifies the desired type to convert the specified expression to. For example, if you want the type of the expression x/y to be int, you can write

double x, y;
// ...
int i = (int) (x / y) ;

Here, even though x and y are of type double, the cast converts the outcome of the expression to int. The parentheses surrounding x / y are necessary. Otherwise, the cast to int would apply only to the x, and not to the outcome of the division. The cast is necessary here because there is no automatic conversion from double to int.

When a cast involves a narrowing conversion, information might be lost. For example, when casting a long into an int, information will be lost if the long’s value is greater than the range of an int, because its high-order bits are removed. When a floating-point value is cast to an integer type, the fractional component will also be lost due to truncation. For example, if the value 1.23 is assigned to an integer, the resulting value will simply be 1. The 0.23 is lost.

The following program demonstrates some type conversions that require casts. It also shows some situations in which the casts cause data to be lost.

// Demonstrate casting.

using System;

class CastDemo {
 public static void Main() {
   double x, y;
   byte b;
   int i;
   char ch;
   uint u;
   short s;
   long l;

   x = 10.0;
   y = 3.0;
   // cast an int into a double
   i = (int) (x / y); // cast double to int, fractional component lost
   Console.WriteLine("Integer outcome of x / y: " + i);
   Console.WriteLine();

   // cast an int into a byte, no data lost
   i = 255;
   b = (byte) i;
   Console.WriteLine("b after assigning 255: " + b +
                     " -- no data lost.");

   // cast an int into a byte, data lost
   i = 257;
   b = (byte) i;
   Console.WriteLine("b after assigning 257: " + b +
                     " -- data lost.");
   Console.WriteLine();

   // cast a uint into a short, no data lost
   u = 32000;
   s = (short) u;
   Console.WriteLine("s after assigning 32000: " + s +
                     " -- no data lost.");

   // cast a uint into a short, data lost
   u = 64000;
   s = (short) u;
   Console.WriteLine("s after assigning 64000: " + s +
                     " -- data lost.");
   Console.WriteLine();

   // cast a long into a uint, no data lost
   l = 64000;
   u = (uint) l;
   Console.WriteLine("u after assigning 64000: " + u +
                     " -- no data lost.");

   // cast a long into a uint, data lost
   l = -12;
   u = (uint) l;
   Console.WriteLine("u after assigning -12: " + u +
                     " -- data lost.");
   Console.WriteLine();

   // cast an int into a char
   b = 88; // ASCII code for X
   ch = (char) b;
   Console.WriteLine("ch after assigning 88: " + ch);
 }
}

The output from the program is shown here:

Integer outcome of x / y: 3

b after assigning 255: 255 -- no data lost.
b after assigning 257: 1 -- data lost.

s after assigning 32000: 32000 -- no data lost.
s after assigning 64000: -1536 -- data lost.

u after assigning 64000: 64000 -- no data lost.
u after assigning -12: 4294967284 -- data lost.

ch after assigning 88: X

Let’s look at each assignment. The cast of (x / y) to int results in the truncation of the fractional component, and information is lost.

No loss of information occurs when b is assigned the value 255 because a byte can hold the value 255. However, when the attempt is made to assign b the value 257, information loss occurs because 257 exceeds a byte’s range. In both cases the casts are needed because there is no automatic conversion from int to byte.

When the short variable s is assigned the value 32,000 through the uint variable u, no data is lost because a short can hold the value 32,000. However, in the next assignment, u has the value 64,000, which is outside the range of a short, and data is lost. In both cases the casts are needed because there is no automatic conversion from uint to short.

Next, u is assigned the value 64,000 through the long variable l. In this case, no data is lost because 64,000 is within the range of a uint. However, when the value 12 is assigned to u, data is lost because a uint cannot hold negative numbers. In both cases the casts are needed because there is no automatic conversion from long to uint.

Finally, no information is lost, but a cast is needed when assigning a byte value to a char.

The Scope and Lifetime of Variables in C sharp

So far, all of the variables that we have been using were declared at the start of the Main( ) method. However, C# allows a local variable to be declared within any block. A block is begun with an opening curly brace and ended by a closing curly brace. A block defines a declaration space, or scope. Thus, each time you start a new block, you are creating a new scope. A scope determines what objects are visible to other parts of your program. It also determines the lifetime of those objects.

The most important scopes in C# are those defined by a class and those defined by a method. A discussion of class scope (and variables declared within it) is deferred until later in this book, when classes are described. For now, we will examine only the scopes defined by or within a method.

The scope defined by a method begins with its opening curly brace. However, if that method has parameters, they too are included within the method’s scope.

As a general rule, variables declared inside a scope are not visible (that is, accessible) to code that is defined outside that scope. Thus, when you declare a variable within a scope, you are localizing that variable and protecting it from unauthorized access and/or modification. Indeed, the scope rules provide the foundation for encapsulation.

Scopes can be nested. For example, each time you create a block of code, you are creating a new, nested scope. When this occurs, the outer scope encloses the inner scope. This means that objects declared in the outer scope will be visible to code within the inner scope. However, the reverse is not true. Objects declared within the inner scope will not be visible outside it.

To understand the effect of nested scopes, consider the following program:

// Demonstrate block scope.

using System;

class ScopeDemo {
 public static void Main() {
   int x; // known to all code within Main()

   x = 10;
   if(x == 10) { // start new scope
     int y = 20; // known only to this block

     // x and y both known here.
     Console.WriteLine("x and y: " + x + " " + y);
     x = y * 2;
   }
   // y = 100; // Error! y not known here

   // x is still known here.
   Console.WriteLine("x is " + x);
 }
}

As the comments indicate, the variable x is declared at the start of Main( )’s scope and is accessible to all subsequent code within Main( ). Within the if block, y is declared. Since a block defines a scope, y is visible only to other code within its block. This is why outside of its block, the line y = 100; is commented out. If you remove the leading comment symbol, a compile-time error will occur, because y is not visible outside of its block. Within the if block, x can be used because code within a block (that is, a nested scope) has access to variables declared by an enclosing scope.

Within a block, variables can be declared at any point, but are valid only after they are declared. Thus, if you define a variable at the start of a method, it is available to all of the code within that method. Conversely, if you declare a variable at the end of a block, it is effectively useless, because no code will have access to it.

Here is another important point to remember: Variables are created when their scope is entered and destroyed when their scope is left. This means that a variable will not hold its value once it has gone out of scope. Therefore, variables declared within a method will not hold their values between calls to that method. Also, a variable declared within a block will lose its value when the block is left. Thus, the lifetime of a variable is confined to its scope.

If a variable declaration includes an initializer, then that variable will be reinitialized each time the block in which it is declared is entered. For example, consider this program:

// Demonstrate lifetime of a variable.

using System;

class VarInitDemo {
 public static void Main() {
   int x;

   for(x = 0; x < 3; x++) {
     int y = -1; // y is initialized each time block is entered
     Console.WriteLine("y is: " + y); // this always prints -1
     y = 100;
     Console.WriteLine("y is now: " + y);
   }
 }
}

The output generated by this program is shown here:

y is: -1
y is now: 100
y is: -1
y is now: 100
y is: -1
y is now: 100

As you can see, y is always reinitialized to 1 each time the body of the for loop is entered. Even though it is subsequently assigned the value 100, this value is lost.

There is one quirk to C#’s scope rules that may surprise you: Although blocks can be nested, no variable declared within an inner scope can have the same name as a variable declared by an enclosing scope. For example, the following program, which tries to declare two separate variables with the same name, will not compile:

/*
  This program attempts to declare a variable
  in an inner scope with the same name as one
  defined in an outer scope.

  *** This program will not compile. ***
*/

using System;

class NestVar {
 public static void Main() {
   int count;

   for(count = 0; count < 10; count = count+1) {
     Console.WriteLine("This is count: " + count);

     int count; // illegal!!!
     for(count = 0; count < 2; count++)
       Console.WriteLine("This program is in error!");
   }
 }
}

If you come from a C/C++ background, then you know that there is no restriction on the names that you give variables declared in an inner scope. Thus, in C/C++ the declaration of count within the block of the outer for loop is completely valid. However, in C/C++, such a declaration hides the outer variable. The designers of C# felt that this name hiding could easily lead to programming errors and disallowed it.

A Closer Look at Variables in C#

Variables are declared using this form of statement:

type var-name;

where type is the data type of the variable and var-name is its name. You can declare a variable of any valid type, including the value types just described. When you create a variable, you are creating an instance of its type. Thus, the capabilities of a variable are determined by its type. For example, a variable of type bool cannot be used to store floating-point values. Furthermore, the type of a variable cannot change during its lifetime. An int variable cannot turn into a char variable, for example.

All variables in C# must be declared prior to their use. This is necessary because the compiler must know what type of data a variable contains before it can properly compile any statement that uses the variable. It also enables C# to perform strict type-checking.

C# defines several different kinds of variables. The kind that we have been using are called local variables because they are declared within a method.

Initializing a Variable

You must give a variable a value prior to using it. One way to give a variable a value is through an assignment statement, as you have already seen. Another way is by giving it an initial value when it is declared. To do this, follow the variable’s name with an equal sign and the value being assigned. The general form of initialization is shown here:

type var = value;

Here, value is the value that is given to var when var is created. The value must be compatible with the specified type.

Here are some examples:

int count = 10; // give count an initial value of 10
char ch = 'X';  // initialize ch with the letter X
float f = 1.2F; // f is initialized with 1.2

When declaring two or more variables of the same type using a comma-separated list, you can give one or more of those variables an initial value. For example:

int a, b = 8, c = 19, d; // b and c have initializations

In this case, only b and c are initialized.

Dynamic Initialization

Although the preceding examples have used only constants as initializers, C# allows variables to be initialized dynamically, using any expression valid at the time the variable is declared. For example, here is a short program that computes the hypotenuse of a right triangle given the lengths of its two opposing sides:

// Demonstrate dynamic initialization.

using System;

class DynInit {
 public static void Main() {
   double s1 = 4.0, s2 = 5.0; // length of sides

   // dynamically initialize hypot
   double hypot = Math.Sqrt( (s1 * s1) + (s2 * s2) );

   Console.Write("Hypotenuse of triangle with sides " +
                 s1 + " by " + s2 + " is ");

   Console.WriteLine("{0:#.###}.", hypot);

 }
}

Here is the output:

Hypotenuse of triangle with sides 4 by 5 is 6.403.

Here, three local variables—s1, s2, and hypot—are declared. The first two, s1 and s2, are initialized by constants. However, hypot is initialized dynamically to the length of the hypotenuse. Notice that the initialization involves calling Math.Sqrt( ). As explained, you can use any expression valid at the time of the declaration. Since a call to Math.Sqrt( ) (or any other library method) is valid at this point, it can be used in the initialization of hypot. The key point here is that the initialization expression can use any element valid at the time of the initialization, including calls to methods, other variables, or literals.

LocalsAdda.com-Variety In Web World

Fun Mail - Fun in the Mail