달력

5

« 2024/5 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
2017. 5. 10. 17:01

C# Programming Guide - Types 프로그래밍/C#2017. 5. 10. 17:01


  1. Types
  2. Casting and Type Conversions
  3. Boxing and Unboxing
  4. Using Type dynamic
  5. How to: Safely Cast by Using as and is Operators
  6. How to: Convert a byte Array to an int
  7. How to: Convert a String to a Number
  8. How to: Convert Between Hexadecimal Strings and Numeric Types



https://docs.microsoft.com/en-us/dotnet/articles/csharp/programming-guide/types/index



Types


Types, Variables, and Values

C# is a strongly-typed language. Every variable and constant has a type, as does every expression that evaluates to a value. Every method signature specifies a type for each input parameter and for the return value. The .NET Framework class library defines a set of built-in numeric types as well as more complex types that represent a wide variety of logical constructs, such as the file system, network connections, collections and arrays of objects, and dates. A typical C# program uses types from the class library as well as user-defined types that model the concepts that are specific to the program's problem domain.

The information stored in a type can include the following:

  • The storage space that a variable of the type requires.

  • The maximum and minimum values that it can represent.

  • The members (methods, fields, events, and so on) that it contains.

  • The base type it inherits from.

  • The location where the memory for variables will be allocated at run time.

  • The kinds of operations that are permitted.

The compiler uses type information to make sure that all operations that are performed in your code are type safe. For example, if you declare a variable of type int, the compiler allows you to use the variable in addition and subtraction operations. If you try to perform those same operations on a variable of type bool, the compiler generates an error, as shown in the following example:

C#
int a = 5;             
int b = a + 2; //OK

bool test = true;
  
// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;
Note

C and C++ developers, notice that in C#, bool is not convertible to int.

The compiler embeds the type information into the executable file as metadata. The common language runtime (CLR) uses that metadata at run time to further guarantee type safety when it allocates and reclaims memory.

Specifying Types in Variable Declarations

When you declare a variable or constant in a program, you must either specify its type or use the var keyword to let the compiler infer the type. The following example shows some variable declarations that use both built-in numeric types and complex user-defined types:

C#
// Declaration only:
float temperature;
string name;
MyClass myClass;

// Declaration with initializers (four examples):
char firstLetter = 'C';
var limit = 3;
int[] source = { 0, 1, 2, 3, 4, 5 };
var query = from item in source
            where item <= limit
            select item;

The types of method parameters and return values are specified in the method signature. The following signature shows a method that requires an int as an input argument and returns a string:

C#
public string GetName(int ID)
{
    if (ID < names.Length)
        return names[ID];
    else
        return String.Empty;
}
private string[] names = { "Spencer", "Sally", "Doug" };

After a variable is declared, it cannot be re-declared with a new type, and it cannot be assigned a value that is not compatible with its declared type. For example, you cannot declare an int and then assign it a Boolean value of true. However, values can be converted to other types, for example when they are assigned to new variables or passed as method arguments. A type conversion that does not cause data loss is performed automatically by the compiler. A conversion that might cause data loss requires a cast in the source code.

For more information, see Casting and Type Conversions.

Built-in Types

C# provides a standard set of built-in numeric types to represent integers, floating point values, Boolean expressions, text characters, decimal values, and other types of data. There are also built-in string and object types. These are available for you to use in any C# program. For a more information about the built-in types, see Reference Tables for Types.

Custom Types

You use the structclassinterface, and enum constructs to create your own custom types. The .NET Framework class library itself is a collection of custom types provided by Microsoft that you can use in your own applications. By default, the most frequently used types in the class library are available in any C# program. Others become available only when you explicitly add a project reference to the assembly in which they are defined. After the compiler has a reference to the assembly, you can declare variables (and constants) of the types declared in that assembly in source code. For more information, see .NET Framework Class Library.

The Common Type System

It is important to understand two fundamental points about the type system in the .NET Framework:

  • It supports the principle of inheritance. Types can derive from other types, called base types. The derived type inherits (with some restrictions) the methods, properties, and other members of the base type. The base type can in turn derive from some other type, in which case the derived type inherits the members of both base types in its inheritance hierarchy. All types, including built-in numeric types such as System.Int32 (C# keyword: int), derive ultimately from a single base type, which is System.Object (C# keyword: object). This unified type hierarchy is called the Common Type System (CTS). For more information about inheritance in C#, see Inheritance.

  • Each type in the CTS is defined as either a value type or a reference type. This includes all custom types in the .NET Framework class library and also your own user-defined types. Types that you define by using the struct keyword are value types; all the built-in numeric types are structs. Types that you define by using the class keyword are reference types. Reference types and value types have different compile-time rules, and different run-time behavior.

The following illustration shows the relationship between value types and reference types in the CTS.

Value Types and Reference Types
Value types and reference types in the CTS

Note

You can see that the most commonly used types are all organized in the System namespace. However, the namespace in which a type is contained has no relation to whether it is a value type or reference type.

Value Types

Value types derive from System.ValueType, which derives from System.Object. Types that derive from System.ValueType have special behavior in the CLR. Value type variables directly contain their values, which means that the memory is allocated inline in whatever context the variable is declared. There is no separate heap allocation or garbage collection overhead for value-type variables.

There are two categories of value types: struct and enum.

The built-in numeric types are structs, and they have properties and methods that you can access:

C#
// Static method on type Byte.  
byte b = Byte.MaxValue;  

But you declare and assign values to them as if they were simple non-aggregate types:

C#
byte num = 0xA;  
int i = 5;  
char c = 'Z';  

Value types are sealed, which means, for example, that you cannot derive a type from System.Int32, and you cannot define a struct to inherit from any user-defined class or struct because a struct can only inherit from System.ValueType. However, a struct can implement one or more interfaces. You can cast a struct type to an interface type; this causes a boxing operation to wrap the struct inside a reference type object on the managed heap. Boxing operations occur when you pass a value type to a method that takes a System.Object as an input parameter. For more information, see Boxing and Unboxing.

You use the struct keyword to create your own custom value types. Typically, a struct is used as a container for a small set of related variables, as shown in the following example:

C#
public struct CoOrds
{
    public int x, y;

    public CoOrds(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}

For more information about structs, see Structs. For more information about value types in the .NET Framework, see Common Type System.

The other category of value types is enum. An enum defines a set of named integral constants. For example, the System.IO.FileMode enumeration in the .NET Framework class library contains a set of named constant integers that specify how a file should be opened. It is defined as shown in the following example:

C#
public enum FileMode
{
    CreateNew = 1,
    Create = 2,
    Open = 3,
    OpenOrCreate = 4,
    Truncate = 5,
    Append = 6,
}

The System.IO.FileMode.Create constant has a value of 2. However, the name is much more meaningful for humans reading the source code, and for that reason it is better to use enumerations instead of constant literal numbers. For more information, see System.IO.FileMode.

All enums inherit from System.Enum, which inherits from System.ValueType. All the rules that apply to structs also apply to enums. For more information about enums, see Enumeration Types.

Reference Types

A type that is defined as a classdelegate, array, or interface is a reference type. At run time, when you declare a variable of a reference type, the variable contains the value null until you explicitly create an instance of the object by using the new operator, or assign it an object that has been created elsewhere by using new, as shown in the following example:

C#
MyClass mc = new MyClass();  
MyClass mc2 = mc;  

An interface must be initialized together with a class object that implements it. If MyClass implements IMyInterface, you create an instance of IMyInterface as shown in the following example:

C#
IMyInterface iface = new MyClass();  

When the object is created, the memory is allocated on the managed heap, and the variable holds only a reference to the location of the object. Types on the managed heap require overhead both when they are allocated and when they are reclaimed by the automatic memory management functionality of the CLR, which is known as garbage collection. However, garbage collection is also highly optimized, and in most scenarios it does not create a performance issue. For more information about garbage collection, see Automatic Memory Management.

All arrays are reference types, even if their elements are value types. Arrays implicitly derive from the System.Arrayclass, but you declare and use them with the simplified syntax that is provided by C#, as shown in the following example:

C#
// Declare and initialize an array of integers.
int[] nums = { 1, 2, 3, 4, 5 };

// Access an instance property of System.Array.
int len = nums.Length;

Reference types fully support inheritance. When you create a class, you can inherit from any other interface or class that is not defined as sealed, and other classes can inherit from your class and override your virtual methods. For more information about how to create your own classes, see Classes and Structs. For more information about inheritance and virtual methods, see Inheritance.

Types of Literal Values

In C#, literal values receive a type from the compiler. You can specify how a numeric literal should be typed by appending a letter to the end of the number. For example, to specify that the value 4.56 should be treated as a float, append an "f" or "F" after the number: 4.56f. If no letter is appended, the compiler will infer a type for the literal. For more information about which types can be specified with letter suffixes, see the reference pages for individual types in Value Types.

Because literals are typed, and all types derive ultimately from System.Object, you can write and compile code such as the following:

C#
string s = "The answer is " + 5.ToString();
// Outputs: "The answer is 5"
Console.WriteLine(s);

Type type = 12345.GetType();
// Outputs: "System.Int32"
Console.WriteLine(type);

Generic Types

A type can be declared with one or more type parameters that serve as a placeholder for the actual type (the concrete type) that client code will provide when it creates an instance of the type. Such types are called generic types. For example, the .NET Framework type System.Collections.Generic.List<T> has one type parameter that by convention is given the name T. When you create an instance of the type, you specify the type of the objects that the list will contain, for example, string:

C#
List<string> stringList = new List<string>();
stringList.Add("String example");
// compile time error adding a type other than a string:
stringList.Add(4);

The use of the type parameter makes it possible to reuse the same class to hold any type of element, without having to convert each element to object. Generic collection classes are called strongly-typed collections because the compiler knows the specific type of the collection's elements and can raise an error at compile-time if, for example, you try to add an integer to the strings object in the previous example. For more information, see Generics.

Implicit Types, Anonymous Types, and Nullable Types

As stated previously, you can implicitly type a local variable (but not class members) by using the var keyword. The variable still receives a type at compile time, but the type is provided by the compiler. For more information, see Implicitly Typed Local Variables.

In some cases, it is inconvenient to create a named type for simple sets of related values that you do not intend to store or pass outside method boundaries. You can create anonymous types for this purpose. For more information, see Anonymous Types.

Ordinary value types cannot have a value of null. However, you can create nullable value types by affixing a ? after the type. For example, int? is an int type that can also have the value null. In the CTS, nullable types are instances of the generic struct type System.Nullable<T>. Nullable types are especially useful when you are passing data to and from databases in which numeric values might be null. For more information, see Nullable Types.

For more information, see the following topics:






Casting and Type Conversions


Because C# is statically-typed at compile time, after a variable is declared, it cannot be declared again or used to store values of another type unless that type is convertible to the variable's type. For example, there is no conversion from an integer to any arbitrary string. Therefore, after you declare i as an integer, you cannot assign the string "Hello" to it, as is shown in the following code.

C#
int i;  
i = "Hello"; // Error: "Cannot implicitly convert type 'string' to 'int'"  

However, you might sometimes need to copy a value into a variable or method parameter of another type. For example, you might have an integer variable that you need to pass to a method whose parameter is typed as double. Or you might need to assign a class variable to a variable of an interface type. These kinds of operations are called type conversions. In C#, you can perform the following kinds of conversions:

  • Implicit conversions: No special syntax is required because the conversion is type safe and no data will be lost. Examples include conversions from smaller to larger integral types, and conversions from derived classes to base classes.

  • Explicit conversions (casts): Explicit conversions require a cast operator. Casting is required when information might be lost in the conversion, or when the conversion might not succeed for other reasons. Typical examples include numeric conversion to a type that has less precision or a smaller range, and conversion of a base-class instance to a derived class.

  • User-defined conversions: User-defined conversions are performed by special methods that you can define to enable explicit and implicit conversions between custom types that do not have a base class–derived class relationship. For more information, see Conversion Operators.

  • Conversions with helper classes: To convert between non-compatible types, such as integers and System.DateTime objects, or hexadecimal strings and byte arrays, you can use the System.BitConverterclass, the System.Convert class, and the Parse methods of the built-in numeric types, such as Int32.Parse. For more information, see How to: Convert a byte Array to an intHow to: Convert a String to a Number, and How to: Convert Between Hexadecimal Strings and Numeric Types.

Implicit Conversions

For built-in numeric types, an implicit conversion can be made when the value to be stored can fit into the variable without being truncated or rounded off. For example, a variable of type long (8 byte integer) can store any value that an int (4 bytes on a 32-bit computer) can store. In the following example, the compiler implicitly converts the value on the right to a type long before assigning it to bigNum.

C#
// Implicit conversion. num long can
// hold any value an int can hold, and more!
int num = 2147483647;
long bigNum = num;

For a complete list of all implicit numeric conversions, see Implicit Numeric Conversions Table.

For reference types, an implicit conversion always exists from a class to any one of its direct or indirect base classes or interfaces. No special syntax is necessary because a derived class always contains all the members of a base class.

Code
Derived d = new Derived();  
Base b = d; // Always OK.  

Explicit Conversions

However, if a conversion cannot be made without a risk of losing information, the compiler requires that you perform an explicit conversion, which is called a cast. A cast is a way of explicitly informing the compiler that you intend to make the conversion and that you are aware that data loss might occur. To perform a cast, specify the type that you are casting to in parentheses in front of the value or variable to be converted. The following program casts a double to an int. The program will not compile without the cast.

C#
class Test
{
    static void Main()
    {
        double x = 1234.7;
        int a;
        // Cast double to int.
        a = (int)x;
        System.Console.WriteLine(a);
    }
}
// Output: 1234

For a list of the explicit numeric conversions that are allowed, see Explicit Numeric Conversions Table.

For reference types, an explicit cast is required if you need to convert from a base type to a derived type:

C#
// Create a new derived type.  
Giraffe g = new Giraffe();  

// Implicit conversion to base type is safe.  
Animal a = g;  

// Explicit conversion is required to cast back  
// to derived type. Note: This will compile but will  
// throw an exception at run time if the right-side  
// object is not in fact a Giraffe.  
Giraffe g2 = (Giraffe) a;  

A cast operation between reference types does not change the run-time type of the underlying object; it only changes the type of the value that is being used as a reference to that object. For more information, see Polymorphism.

Type Conversion Exceptions at Run Time

In some reference type conversions, the compiler cannot determine whether a cast will be valid. It is possible for a cast operation that compiles correctly to fail at run time. As shown in the following example, a type cast that fails at run time will cause an InvalidCastException to be thrown.

C#

using System;

class Animal
{
    public void Eat() { Console.WriteLine("Eating."); }
    public override string ToString()
    {
        return "I am an animal.";
    }
}
class Reptile : Animal { }
class Mammal : Animal { }

class UnSafeCast
{
    static void Main()
    {            
        Test(new Mammal());

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }

    static void Test(Animal a)
    {
        // Cause InvalidCastException at run time 
        // because Mammal is not convertible to Reptile.
        Reptile r = (Reptile)a;
    }

}

C# provides the is and as operators to enable you to test for compatibility before actually performing a cast. For more information, see How to: Safely Cast by Using as and is Operators.






Boxing and Unboxing


Boxing is the process of converting a value type to the type object or to any interface type implemented by this value type. When the CLR boxes a value type, it wraps the value inside a System.Object and stores it on the managed heap. Unboxing extracts the value type from the object. Boxing is implicit; unboxing is explicit. The concept of boxing and unboxing underlies the C# unified view of the type system in which a value of any type can be treated as an object.

In the following example, the integer variable i is boxed and assigned to object o.

C#
int i = 123;
// The following line boxes i.
object o = i;  

The object o can then be unboxed and assigned to integer variable i:

C#
o = 123;
i = (int)o;  // unboxing

The following examples illustrate how boxing is used in C#.

C#
// String.Concat example.
// String.Concat has many versions. Rest the mouse pointer on 
// Concat in the following statement to verify that the version
// that is used here takes three object arguments. Both 42 and
// true must be boxed.
Console.WriteLine(String.Concat("Answer", 42, true));


// List example.
// Create a list of objects to hold a heterogeneous collection 
// of elements.
List<object> mixedList = new List<object>();

// Add a string element to the list. 
mixedList.Add("First Group:");

// Add some integers to the list. 
for (int j = 1; j < 5; j++)
{
    // Rest the mouse pointer over j to verify that you are adding
    // an int to a list of objects. Each element j is boxed when 
    // you add j to mixedList.
    mixedList.Add(j);
}

// Add another string and more integers.
mixedList.Add("Second Group:");
for (int j = 5; j < 10; j++)
{
    mixedList.Add(j);
}

// Display the elements in the list. Declare the loop variable by 
// using var, so that the compiler assigns its type.
foreach (var item in mixedList)
{
    // Rest the mouse pointer over item to verify that the elements
    // of mixedList are objects.
    Console.WriteLine(item);
}

// The following loop sums the squares of the first group of boxed
// integers in mixedList. The list elements are objects, and cannot
// be multiplied or added to the sum until they are unboxed. The
// unboxing must be done explicitly.
var sum = 0;
for (var j = 1; j < 5; j++)
{
    // The following statement causes a compiler error: Operator 
    // '*' cannot be applied to operands of type 'object' and
    // 'object'. 
    //sum += mixedList[j] * mixedList[j]);

    // After the list elements are unboxed, the computation does 
    // not cause a compiler error.
    sum += (int)mixedList[j] * (int)mixedList[j];
}

// The sum displayed is 30, the sum of 1 + 4 + 9 + 16.
Console.WriteLine("Sum: " + sum);

// Output:
// Answer42True
// First Group:
// 1
// 2
// 3
// 4
// Second Group:
// 5
// 6
// 7
// 8
// 9
// Sum: 30

Performance

In relation to simple assignments, boxing and unboxing are computationally expensive processes. When a value type is boxed, a new object must be allocated and constructed. To a lesser degree, the cast required for unboxing is also expensive computationally. For more information, see Performance.

Boxing

Boxing is used to store value types in the garbage-collected heap. Boxing is an implicit conversion of a value typeto the type object or to any interface type implemented by this value type. Boxing a value type allocates an object instance on the heap and copies the value into the new object.

Consider the following declaration of a value-type variable:

C#
int i = 123;

The following statement implicitly applies the boxing operation on the variable i:

C#
// Boxing copies the value of i into object o.
object o = i;  

The result of this statement is creating an object reference o, on the stack, that references a value of the type int, on the heap. This value is a copy of the value-type value assigned to the variable i. The difference between the two variables, i and o, is illustrated in the following figure.

BoxingConversion graphic
Boxing Conversion

It is also possible to perform the boxing explicitly as in the following example, but explicit boxing is never required:

C#
int i = 123;
object o = (object)i;  // explicit boxing

Description

This example converts an integer variable i to an object o by using boxing. Then, the value stored in the variable i is changed from 123 to 456. The example shows that the original value type and the boxed object use separate memory locations, and therefore can store different values.

Example

C#
class TestBoxing
{
    static void Main()
    {
        int i = 123;

        // Boxing copies the value of i into object o.
        object o = i;  

        // Change the value of i.
        i = 456;  

        // The change in i does not effect the value stored in o.
        System.Console.WriteLine("The value-type value = {0}", i);
        System.Console.WriteLine("The object-type value = {0}", o);
    }
}
/* Output:
    The value-type value = 456
    The object-type value = 123
*/

Unboxing

Unboxing is an explicit conversion from the type object to a value type or from an interface type to a value type that implements the interface. An unboxing operation consists of:

  • Checking the object instance to make sure that it is a boxed value of the given value type.

  • Copying the value from the instance into the value-type variable.

The following statements demonstrate both boxing and unboxing operations:

C#
int i = 123;      // a value type
object o = i;     // boxing
int j = (int)o;   // unboxing

The following figure demonstrates the result of the previous statements.

UnBoxing Conversion graphic
Unboxing Conversion

For the unboxing of value types to succeed at run time, the item being unboxed must be a reference to an object that was previously created by boxing an instance of that value type. Attempting to unbox null causes a NullReferenceException. Attempting to unbox a reference to an incompatible value type causes an InvalidCastException.

Example

The following example demonstrates a case of invalid unboxing and the resulting InvalidCastException. Using try and catch, an error message is displayed when the error occurs.

C#
class TestUnboxing
{
    static void Main()
    {
        int i = 123;
        object o = i;  // implicit boxing

        try
        {
            int j = (short)o;  // attempt to unbox

            System.Console.WriteLine("Unboxing OK.");
        }
        catch (System.InvalidCastException e)
        {
            System.Console.WriteLine("{0} Error: Incorrect unboxing.", e.Message);
        }
    }
}

This program outputs:

Specified cast is not valid. Error: Incorrect unboxing.

If you change the statement:

Code
int j = (short) o;  

to:

Code
int j = (int) o;  

the conversion will be performed, and you will get the output:

Unboxing OK.






Using Type dynamic


Visual C# 2010 introduces a new type, dynamic. The type is a static type, but an object of type dynamic bypasses static type checking. In most cases, it functions like it has type object. At compile time, an element that is typed as dynamic is assumed to support any operation. Therefore, you do not have to be concerned about whether the object gets its value from a COM API, from a dynamic language such as IronPython, from the HTML Document Object Model (DOM), from reflection, or from somewhere else in the program. However, if the code is not valid, errors are caught at run time.

For example, if instance method exampleMethod1 in the following code has only one parameter, the compiler recognizes that the first call to the method, ec.exampleMethod1(10, 4), is not valid because it contains two arguments. The call causes a compiler error. The second call to the method, dynamic_ec.exampleMethod1(10, 4), is not checked by the compiler because the type of dynamic_ec is dynamic. Therefore, no compiler error is reported. However, the error does not escape notice indefinitely. It is caught at run time and causes a run-time exception.

C#
static void Main(string[] args)
{
    ExampleClass ec = new ExampleClass();
    // The following call to exampleMethod1 causes a compiler error 
    // if exampleMethod1 has only one parameter. Uncomment the line
    // to see the error.
    //ec.exampleMethod1(10, 4);

    dynamic dynamic_ec = new ExampleClass();
    // The following line is not identified as an error by the
    // compiler, but it causes a run-time exception.
    dynamic_ec.exampleMethod1(10, 4);

    // The following calls also do not cause compiler errors, whether 
    // appropriate methods exist or not.
    dynamic_ec.someMethod("some argument", 7, null);
    dynamic_ec.nonexistentMethod();
}
C#
class ExampleClass
{
    public ExampleClass() { }
    public ExampleClass(int v) { }

    public void exampleMethod1(int i) { }

    public void exampleMethod2(string str) { }
}

The role of the compiler in these examples is to package together information about what each statement is proposing to do to the object or expression that is typed as dynamic. At run time, the stored information is examined, and any statement that is not valid causes a run-time exception.

The result of most dynamic operations is itself dynamic. For example, if you rest the mouse pointer over the use of testSum in the following example, IntelliSense displays the type (local variable) dynamic testSum.

C#
dynamic d = 1;
var testSum = d + 3;
// Rest the mouse pointer over testSum in the following statement.
System.Console.WriteLine(testSum);

Operations in which the result is not dynamic include conversions from dynamic to another type, and constructor calls that include arguments of type dynamic. For example, the type of testInstance in the following declaration is ExampleClass, not dynamic.

C#
var testInstance = new ExampleClass(d);

Conversion examples are shown in the following section, "Conversions."

Conversions

Conversions between dynamic objects and other types are easy. This enables the developer to switch between dynamic and non-dynamic behavior.

Any object can be converted to dynamic type implicitly, as shown in the following examples.

C#
dynamic d1 = 7;
dynamic d2 = "a string";
dynamic d3 = System.DateTime.Today;
dynamic d4 = System.Diagnostics.Process.GetProcesses();

Conversely, an implicit conversion can be dynamically applied to any expression of type dynamic.

C#
int i = d1;
string str = d2;
DateTime dt = d3;
System.Diagnostics.Process[] procs = d4;

Overload Resolution with Arguments of Type dynamic

Overload resolution occurs at run time instead of at compile time if one or more of the arguments in a method call have the type dynamic, or if the receiver of the method call is of type dynamic. In the following example, if the only accessible exampleMethod2 method is defined to take a string argument, sending d1 as the argument does not cause a compiler error, but it does cause a run-time exception. Overload resolution fails at run time because the run-time type of d1 is int, and exampleMethod2 requires a string.

C#
// Valid.
ec.exampleMethod2("a string");

// The following statement does not cause a compiler error, even though ec is not
// dynamic. A run-time exception is raised because the run-time type of d1 is int.
ec.exampleMethod2(d1);
// The following statement does cause a compiler error.
//ec.exampleMethod2(7);

Dynamic Language Runtime

The dynamic language runtime (DLR) is a new API in .NET Framework 4. It provides the infrastructure that supports the dynamic type in C#, and also the implementation of dynamic programming languages such as IronPython and IronRuby. For more information about the DLR, see Dynamic Language Runtime Overview.

COM Interop

Visual C# 2010 includes several features that improve the experience of interoperating with COM APIs such as the Office Automation APIs. Among the improvements are the use of the dynamic type, and of named and optional arguments.

Many COM methods allow for variation in argument types and return type by designating the types as object. This has necessitated explicit casting of the values to coordinate with strongly typed variables in C#. If you compile by using the /link (C# Compiler Options) option, the introduction of the dynamic type enables you to treat the occurrences of object in COM signatures as if they were of type dynamic, and thereby to avoid much of the casting. For example, the following statements contrast how you access a cell in a Microsoft Office Excel spreadsheet with the dynamic type and without the dynamic type.

C#
// Before the introduction of dynamic.
((Excel.Range)excelApp.Cells[1, 1]).Value2 = "Name";
Excel.Range range2008 = (Excel.Range)excelApp.Cells[1, 1];
C#
// After the introduction of dynamic, the access to the Value property and
// the conversion to Excel.Range are handled by the run-time COM binder.
excelApp.Cells[1, 1].Value = "Name";
Excel.Range range2010 = excelApp.Cells[1, 1];
TitleDescription
dynamicDescribes the usage of the dynamic keyword.
Dynamic Language Runtime OverviewProvides an overview of the DLR, which is a runtime environment that adds a set of services for dynamic languages to the common language runtime (CLR).
Walkthrough: Creating and Using Dynamic ObjectsProvides step-by-step instructions for creating a custom dynamic object and for creating a project that accesses an IronPython library.
How to: Access Office Interop Objects by Using Visual C# FeaturesDemonstrates how to create a project that uses named and optional arguments, the dynamic type, and other enhancements that simplify access to Office API objects.






How to: Safely Cast by Using as and is Operators


Because objects are polymorphic, it is possible for a variable of a base class type to hold a derived type. To access the derived type's method, it is necessary to cast the value back to the derived type. However, to attempt a simple cast in these cases creates the risk of throwing an InvalidCastException. That is why C# provides the is and asoperators. You can use these operators to test whether a cast will succeed without causing an exception to be thrown. In general, the as operator is more efficient because it actually returns the cast value if the cast can be made successfully. The is operator returns only a Boolean value. It can therefore be used when you just want to determine an object's type but do not have to actually cast it.

Example

The following examples show how to use the is and as operators to cast from one reference type to another without the risk of throwing an exception. The example also shows how to use the as operator with nullable value types.

C#
class SafeCasting
{
    class Animal
    {
        public void Eat() { Console.WriteLine("Eating."); }
        public override string ToString()
        {
            return "I am an animal.";
        }
    }
    class Mammal : Animal { }
    class Giraffe : Mammal { }

    class SuperNova { }

    static void Main()
    {
        SafeCasting app = new SafeCasting();

        // Use the is operator to verify the type.
        // before performing a cast.
        Giraffe g = new Giraffe();
        app.UseIsOperator(g);

        // Use the as operator and test for null
        // before referencing the variable.
        app.UseAsOperator(g);

        // Use the as operator to test
        // an incompatible type.
        SuperNova sn = new SuperNova();
        app.UseAsOperator(sn);

        // Use the as operator with a value type.
        // Note the implicit conversion to int? in 
        // the method body.
        int i = 5;
        app.UseAsWithNullable(i);


        double d = 9.78654;
        app.UseAsWithNullable(d);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }

    void UseIsOperator(Animal a)
    {
        if (a is Mammal)
        {
            Mammal m = (Mammal)a;
            m.Eat();
        }
    }

    void UseAsOperator(object o)
    {
        Mammal m = o as Mammal;
        if (m != null)
        {
            Console.WriteLine(m.ToString());
        }
        else
        {
            Console.WriteLine("{0} is not a Mammal", o.GetType().Name);
        }
    }

    void UseAsWithNullable(System.ValueType val)
    {
        int? j = val as int?;
        if (j != null)
        {
            Console.WriteLine(j);
        }
        else
        {
            Console.WriteLine("Could not convert " + val.ToString());
        }
    }
}






How to: Convert a byte Array to an int


This example shows you how to use the BitConverter class to convert an array of bytes to an int and back to an array of bytes. You may have to convert from bytes to a built-in data type after you read bytes off the network, for example. In addition to the ToInt32(Byte[], Int32) method in the example, the following table lists methods in the BitConverter class that convert bytes (from an array of bytes) to other built-in types.

Type returnedMethod
boolToBoolean(Byte[], Int32)
charToChar(Byte[], Int32)
doubleToDouble(Byte[], Int32)
shortToInt16(Byte[], Int32)
intToInt32(Byte[], Int32)
longToInt64(Byte[], Int32)
floatToSingle(Byte[], Int32)
ushortToUInt16(Byte[], Int32)
uintToUInt32(Byte[], Int32)
ulongToUInt64(Byte[], Int32)

Example

This example initializes an array of bytes, reverses the array if the computer architecture is little-endian (that is, the least significant byte is stored first), and then calls the ToInt32(Byte[], Int32) method to convert four bytes in the array to an int. The second argument to ToInt32(Byte[], Int32) specifies the start index of the array of bytes.

Note

The output may differ depending on the endianess of your computer's architecture.

C#
byte[] bytes = { 0, 0, 0, 25 };

// If the system architecture is little-endian (that is, little end first),
// reverse the byte array.
if (BitConverter.IsLittleEndian)
    Array.Reverse(bytes);

int i = BitConverter.ToInt32(bytes, 0);
Console.WriteLine("int: {0}", i);
// Output: int: 25

Example

In this example, the GetBytes(Int32) method of the BitConverter class is called to convert an int to an array of bytes.

Note

The output may differ depending on the endianess of your computer's architecture.

C#
byte[] bytes = BitConverter.GetBytes(201805978);
Console.WriteLine("byte array: " + BitConverter.ToString(bytes));
// Output: byte array: 9A-50-07-0C






How to: Convert a String to a Number


You can convert a string to a number by using methods in the Convert class or by using the TryParse method found on the various numeric types (int, long, float, etc.).

If you have a string, it is slightly more efficient and straightforward to call a TryParse method (for example, int.TryParse(“11”)). Using a Convert method is more useful for general objects that implement IConvertible.

You can use Parse or TryParse methods on the numeric type you expect the string contains, such as the System.Int32 type. The Convert.ToUInt32 method uses Parse internally. If the string is not in a valid format, Parse throws an exception whereas TryParse returns false.

Example

The Parse and TryParse methods ignore whitespace at the beginning and at the end of the string, but all other characters must be characters that form the appropriate numeric type (int, long, ulong, float, decimal, etc.). Any whitespace within the characters that form the number cause an error. For example, you can use decimal.TryParse to parse “10”, “10.3”, “ 10 “, but you cannot use this method to parse 10 from “10X”, “1 0” (note space), “10 .3” (note space), “10e1” (float.TryParse works here), and so on.

The examples below demonstrate both successful and unsuccessful calls to Parse and TryParse.

C#
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
C#
int numVal = Int32.Parse("-105");
Console.WriteLine(numVal);
// Output: -105
C#
// TryParse returns true if the conversion succeeded
// and stores the result in j.
int j;
if (Int32.TryParse("-105", out j))
    Console.WriteLine(j);
else
    Console.WriteLine("String could not be parsed.");
// Output: -105
C#
try
{
    int m = Int32.Parse("abc");
}
catch (FormatException e)
{
    Console.WriteLine(e.Message);
}
// Output: Input string was not in a correct format.
C#
string inputString = "abc";
int numValue;
bool parsed = Int32.TryParse(inputString, out numValue);

if (!parsed)
    Console.WriteLine("Int32.TryParse could not parse '{0}' to an int.\n", inputString);

// Output: Int32.TryParse could not parse 'abc' to an int.
C#
// This snippet shows a couple of examples that extract number characters from the
// beginning of the string to avoid TryParse errors.
StringBuilder sb = new StringBuilder();
var str = "  10FFxxx";
foreach (char c in str) {
    // Check for numeric characters (hex in this case).  Add "." and "e" if float,
    // and remove letters.  Include initial space because it is harmless.
    if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || c == ' ') {
        sb.Append(c);
    }
    else
        break;
}
if (int.TryParse(sb.ToString(), System.Globalization.NumberStyles.HexNumber, null, out i))
    Console.WriteLine(sb.ToString());

str = "   -10FFXXX";
sb.Clear();
foreach (char c in str) {
    // Check for numeric characters (allow negative in this case but no hex digits). 
    // Though we use int.TryParse in the previous example and this one, int.TryParse does NOT
    // allow a sign character (-) AND hex digits at the same time.
    // Include initial space because it is harmless.
    if ((c >= '0' && c <= '9') || c == ' ' || c == '-') {
        sb.Append(c);
    } else
        break;
}
if (int.TryParse(sb.ToString(), out i))
    Console.WriteLine(sb.ToString());

Example

The following table lists some of the methods from the Convert class that you can use.

Numeric TypeMethod
decimalToDecimal(String)
floatToSingle(String)
doubleToDouble(String)
shortToInt16(String)
intToInt32(String)
longToInt64(String)
ushortToUInt16(String)
uintToUInt32(String)
ulongToUInt64(String)

This example calls the Convert.ToInt32(String) method to convert an input string to an int . The code catches the two most common exceptions that can be thrown by this method, FormatException and OverflowException. If the number can be incremented without overflowing the integer storage location, the program adds 1 to the result and prints the output.

C#
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
C#
static void Main(string[] args)
{
    int numVal = -1;
    bool repeat = true;

    while (repeat)
    {
        Console.WriteLine("Enter a number between −2,147,483,648 and +2,147,483,647 (inclusive).");

        string input = Console.ReadLine();

        // ToInt32 can throw FormatException or OverflowException.
        try
        {
            numVal = Convert.ToInt32(input);
        }
        catch (FormatException e)
        {
            Console.WriteLine("Input string is not a sequence of digits.");
        }
        catch (OverflowException e)
        {
            Console.WriteLine("The number cannot fit in an Int32.");
        }
        finally
        {
            if (numVal < Int32.MaxValue)
            {
                Console.WriteLine("The new value is {0}", numVal + 1);
            }
            else
            {
                Console.WriteLine("numVal cannot be incremented beyond its current value");
            }
        }
        Console.WriteLine("Go again? Y/N");
        string go = Console.ReadLine();
        if (go == "Y" || go == "y")
        {
            repeat = true;
        }
        else
        {
            repeat = false;
        }
    }
    // Keep the console open in debug mode.
    Console.WriteLine("Press any key to exit.");
    Console.ReadKey();    
}
// Sample Output:
// Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive).
// 473
// The new value is 474
// Go again? Y/N
// y
// Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive).
// 2147483647
// numVal cannot be incremented beyond its current value
// Go again? Y/N
// Y
// Enter a number between -2,147,483,648 and +2,147,483,647 (inclusive).
// -1000
// The new value is -999
// Go again? Y/N
// n
// Press any key to exit.






How to: Convert Between Hexadecimal Strings and Numeric Types


These examples show you how to perform the following tasks:

  • Obtain the hexadecimal value of each character in a string.

  • Obtain the char that corresponds to each value in a hexadecimal string.

  • Convert a hexadecimal string to an int.

  • Convert a hexadecimal string to a float.

  • Convert a byte array to a hexadecimal string.

Example

This example outputs the hexadecimal value of each character in a string. First it parses the string to an array of characters. Then it calls ToInt32(Char) on each character to obtain its numeric value. Finally, it formats the number as its hexadecimal representation in a string.

C#
string input = "Hello World!";
char[] values = input.ToCharArray();
foreach (char letter in values)
{
    // Get the integral value of the character.
    int value = Convert.ToInt32(letter);
    // Convert the decimal value to a hexadecimal value in string form.
    string hexOutput = String.Format("{0:X}", value);
    Console.WriteLine("Hexadecimal value of {0} is {1}", letter, hexOutput);
}
/* Output:
   Hexadecimal value of H is 48
    Hexadecimal value of e is 65
    Hexadecimal value of l is 6C
    Hexadecimal value of l is 6C
    Hexadecimal value of o is 6F
    Hexadecimal value of   is 20
    Hexadecimal value of W is 57
    Hexadecimal value of o is 6F
    Hexadecimal value of r is 72
    Hexadecimal value of l is 6C
    Hexadecimal value of d is 64
    Hexadecimal value of ! is 21
 */

Example

This example parses a string of hexadecimal values and outputs the character corresponding to each hexadecimal value. First it calls the Split(Char[]) method to obtain each hexadecimal value as an individual string in an array. Then it calls ToInt32(String, Int32) to convert the hexadecimal value to a decimal value represented as an int. It shows two different ways to obtain the character corresponding to that character code. The first technique uses ConvertFromUtf32(Int32), which returns the character corresponding to the integer argument as a string. The second technique explicitly casts the int to a char.

C#
string hexValues = "48 65 6C 6C 6F 20 57 6F 72 6C 64 21";
string[] hexValuesSplit = hexValues.Split(' ');
foreach (String hex in hexValuesSplit)
{
    // Convert the number expressed in base-16 to an integer.
    int value = Convert.ToInt32(hex, 16);
    // Get the character corresponding to the integral value.
    string stringValue = Char.ConvertFromUtf32(value);
    char charValue = (char)value;
    Console.WriteLine("hexadecimal value = {0}, int value = {1}, char value = {2} or {3}",
                        hex, value, stringValue, charValue);
}
/* Output:
    hexadecimal value = 48, int value = 72, char value = H or H
    hexadecimal value = 65, int value = 101, char value = e or e
    hexadecimal value = 6C, int value = 108, char value = l or l
    hexadecimal value = 6C, int value = 108, char value = l or l
    hexadecimal value = 6F, int value = 111, char value = o or o
    hexadecimal value = 20, int value = 32, char value =   or
    hexadecimal value = 57, int value = 87, char value = W or W
    hexadecimal value = 6F, int value = 111, char value = o or o
    hexadecimal value = 72, int value = 114, char value = r or r
    hexadecimal value = 6C, int value = 108, char value = l or l
    hexadecimal value = 64, int value = 100, char value = d or d
    hexadecimal value = 21, int value = 33, char value = ! or !
*/

Example

This example shows another way to convert a hexadecimal string to an integer, by calling the Parse(String, NumberStyles) method.

C#
string hexString = "8E2";
int num = Int32.Parse(hexString, System.Globalization.NumberStyles.HexNumber);
Console.WriteLine(num);
//Output: 2274

Example

The following example shows how to convert a hexadecimal string to a float by using the System.BitConverterclass and the Int32.Parse method.

C#

string hexString = "43480170";
uint num = uint.Parse(hexString, System.Globalization.NumberStyles.AllowHexSpecifier);

byte[] floatVals = BitConverter.GetBytes(num);
float f = BitConverter.ToSingle(floatVals, 0);
Console.WriteLine("float convert = {0}", f);

// Output: 200.0056            

Example

The following example shows how to convert a byte array to a hexadecimal string by using the System.BitConverter class.

C#
byte[] vals = { 0x01, 0xAA, 0xB1, 0xDC, 0x10, 0xDD };

string str = BitConverter.ToString(vals);
Console.WriteLine(str);

str = BitConverter.ToString(vals).Replace("-", "");
Console.WriteLine(str);

/*Output:
  01-AA-B1-DC-10-DD
  01AAB1DC10DD
 */


:
Posted by 지훈2
2017. 5. 10. 15:51

C# Programming Guide - Inside a C# Program 프로그래밍/C#2017. 5. 10. 15:51


  1. Inside a C# Program
  2. Hello World -- Your First Program
  3. General Structure of a C# Program
  4. C# Coding Conventions



https://docs.microsoft.com/en-us/dotnet/articles/csharp/programming-guide/inside-a-program/




Inside a C# Program


The section discusses the general structure of a C# program, and includes the standard "Hello, World!" example.

In This Section






Hello World -- Your First Program


The following procedure creates a C# version of the traditional "Hello World!" program. The program displays the string Hello World!

For more examples of introductory concepts, see Getting Started with Visual C# and Visual Basic.

Note

Your computer might show different names or locations for some of the Visual Studio user interface elements in the following instructions. The Visual Studio edition that you have and the settings that you use determine these elements. For more information, see Personalizing the IDE.

To create and run a console application

  1. Start Visual Studio.

  2. On the menu bar, choose FileNewProject.

    The New Project dialog box opens.

  3. Expand Installed, expand Templates, expand Visual C#, and then choose Console Application.

  4. In the Name box, specify a name for your project, and then choose the OK button.

    The new project appears in Solution Explorer.

  5. If Program.cs isn't open in the Code Editor, open the shortcut menu for Program.cs in Solution Explorer, and then choose View Code.

  6. Replace the contents of Program.cs with the following code.

    C#
    // A Hello World! program in C#.
    using System;
    namespace HelloWorld
    {
        class Hello 
        {
            static void Main() 
            {
                Console.WriteLine("Hello World!");
    
                // Keep the console window open in debug mode.
                Console.WriteLine("Press any key to exit.");
                Console.ReadKey();
            }
        }
    }
    
  7. Choose the F5 key to run the project. A Command Prompt window appears that contains the line Hello World!

Next, the important parts of this program are examined.

Comments

The first line contains a comment. The characters // convert the rest of the line to a comment.

C#
// A Hello World! program in C#.

You can also comment out a block of text by enclosing it between the /* and */ characters. This is shown in the following example.

C#
/* A "Hello World!" program in C#.
This program displays the string "Hello World!" on the screen. */

Main Method

A C# console application must contain a Main method, in which control starts and ends. The Main method is where you create objects and execute other methods.

The Main method is a static method that resides inside a class or a struct. In the previous "Hello World!" example, it resides in a class named Hello. You can declare the Main method in one of the following ways:

  • It can return void.

    C#
    static void Main()
    {
        //...
    }
    
  • It can also return an integer.

    C#
    static int Main()
    {
        //...
        return 0;
    }
    
  • With either of the return types, it can take arguments.

    C#
    static void Main(string[] args)
    {
        //...
    }
    

    -or-

    C#
    static int Main(string[] args)
    {
        //...
        return 0;
    }
    

The parameter of the Main method, args, is a string array that contains the command-line arguments used to invoke the program. Unlike in C++, the array does not include the name of the executable (exe) file.

For more information about how to use command-line arguments, see the examples in Main() and Command-Line Arguments and How to: Create and Use Assemblies Using the Command Line.

The call to ReadKey at the end of the Main method prevents the console window from closing before you have a chance to read the output when you run your program in debug mode, by pressing F5.

Input and Output

C# programs generally use the input/output services provided by the run-time library of the .NET Framework. The statement System.Console.WriteLine("Hello World!"); uses the WriteLine method. This is one of the output methods of the Console class in the run-time library. It displays its string parameter on the standard output stream followed by a new line. Other Console methods are available for different input and output operations. If you include the using System; directive at the beginning of the program, you can directly use the System classes and methods without fully qualifying them. For example, you can call Console.WriteLine instead of System.Console.WriteLine:

C#
using System;
C#
Console.WriteLine("Hello World!");

For more information about input/output methods, see System.IO.

Command-Line Compilation and Execution

You can compile the "Hello World!" program by using the command line instead of the Visual Studio Integrated Development Environment (IDE).

To compile and run from a command prompt

  1. Paste the code from the preceding procedure into any text editor, and then save the file as a text file. Name the file Hello.cs. C# source code files use the extension .cs.

  2. Perform one of the following steps to open a command-prompt window:

    • In Windows 8, on the Start screen, search for Developer Command Prompt, and then tap or choose Developer Command Prompt for VS2012.

      A Developer Command Prompt window appears.

    • In Windows 7, open the Start menu, expand the folder for the current version of Visual Studio, open the shortcut menu for Visual Studio Tools, and then choose Developer Command Prompt for VS2012.

      A Developer Command Prompt window appears.

    • Enable command-line builds from a standard Command Prompt window.

      See How to: Set Environment Variables for the Visual Studio Command Line.

  3. In the command-prompt window, navigate to the folder that contains your Hello.cs file.

  4. Enter the following command to compile Hello.cs.

    csc Hello.cs

    If your program has no compilation errors, an executable file that is named Hello.exe is created.

  5. In the command-prompt window, enter the following command to run the program:

    Hello

  6. +

For more information about the C# compiler and its options, see C# Compiler Options.






General Structure of a C# Program


C# programs can consist of one or more files. Each file can contain zero or more namespaces. A namespace can contain types such as classes, structs, interfaces, enumerations, and delegates, in addition to other namespaces. The following is the skeleton of a C# program that contains all of these elements.

C#
// A skeleton of a C# program 
using System;
namespace YourNamespace
{
    class YourClass
    {
    }

    struct YourStruct
    {
    }

    interface IYourInterface 
    {
    }

    delegate int YourDelegate();

    enum YourEnum 
    {
    }

    namespace YourNestedNamespace
    {
        struct YourStruct 
        {
        }
    }

    class YourMainClass
    {
        static void Main(string[] args) 
        {
            //Your program starts here...
        }
    }
}






C# Coding Conventions


The C# Language Specification does not define a coding standard. However, the guidelines in this topic are used by Microsoft to develop samples and documentation.

Coding conventions serve the following purposes:

  • They create a consistent look to the code, so that readers can focus on content, not layout.

  • They enable readers to understand the code more quickly by making assumptions based on previous experience.

  • They facilitate copying, changing, and maintaining the code.

  • They demonstrate C# best practices.

Naming Conventions

  • In short examples that do not include using directives, use namespace qualifications. If you know that a namespace is imported by default in a project, you do not have to fully qualify the names from that namespace. Qualified names can be broken after a dot (.) if they are too long for a single line, as shown in the following example.

    C#
    var currentPerformanceCounterCategory = new System.Diagnostics.
        PerformanceCounterCategory();
    
  • You do not have to change the names of objects that were created by using the Visual Studio designer tools to make them fit other guidelines.

Layout Conventions

Good layout uses formatting to emphasize the structure of your code and to make the code easier to read. Microsoft examples and samples conform to the following conventions:

  • Use the default Code Editor settings (smart indenting, four-character indents, tabs saved as spaces). For more information, see Options, Text Editor, C#, Formatting.

  • Write only one statement per line.

  • Write only one declaration per line.

  • If continuation lines are not indented automatically, indent them one tab stop (four spaces).

  • Add at least one blank line between method definitions and property definitions.

  • Use parentheses to make clauses in an expression apparent, as shown in the following code.

    C#
    if ((val1 > val2) && (val1 > val3))
    {
        // Take appropriate action.
    }
    

Commenting Conventions

  • Place the comment on a separate line, not at the end of a line of code.

  • Begin comment text with an uppercase letter.

  • End comment text with a period.

  • Insert one space between the comment delimiter (//) and the comment text, as shown in the following example.

    C#
    // The following declaration creates a query. It does not run
    // the query.
    
  • Do not create formatted blocks of asterisks around comments.

Language Guidelines

The following sections describe practices that the C# team follows to prepare code examples and samples.

String Data Type

  • Use the + operator to concatenate short strings, as shown in the following code.

    C#
    string displayName = nameList[n].LastName + ", " + nameList[n].FirstName;
    
  • To append strings in loops, especially when you are working with large amounts of text, use a StringBuilder object.

    C#
    var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
    var manyPhrases = new StringBuilder();
    for (var i = 0; i < 10000; i++)
    {
        manyPhrases.Append(phrase);
    }
    //Console.WriteLine("tra" + manyPhrases);
    

Implicitly Typed Local Variables

  • Use implicit typing for local variables when the type of the variable is obvious from the right side of the assignment, or when the precise type is not important.

    C#
    // When the type of a variable is clear from the context, use var 
    // in the declaration.
    var var1 = "This is clearly a string.";
    var var2 = 27;
    var var3 = Convert.ToInt32(Console.ReadLine());
    
  • Do not use var when the type is not apparent from the right side of the assignment.

    C#
    // When the type of a variable is not clear from the context, use an
    // explicit type.
    int var4 = ExampleClass.ResultSoFar();
    
  • Do not rely on the variable name to specify the type of the variable. It might not be correct.

    C#
    // Naming the following variable inputInt is misleading. 
    // It is a string.
    var inputInt = Console.ReadLine();
    Console.WriteLine(inputInt);
    
  • Avoid the use of var in place of dynamic.

  • Use implicit typing to determine the type of the loop variable in for and foreach loops.

    The following example uses implicit typing in a for statement.

    C#
    var syllable = "ha";
    var laugh = "";
    for (var i = 0; i < 10; i++)
    {
        laugh += syllable;
        Console.WriteLine(laugh);
    }
    

    The following example uses implicit typing in a foreach statement.

    C#
    foreach (var ch in laugh)
    {
        if (ch == 'h')
            Console.Write("H");
        else
            Console.Write(ch);
    }
    Console.WriteLine();
    

Unsigned Data Type

  • In general, use int rather than unsigned types. The use of int is common throughout C#, and it is easier to interact with other libraries when you use int.

Arrays

  • Use the concise syntax when you initialize arrays on the declaration line.

    C#
    // Preferred syntax. Note that you cannot use var here instead of string[].
    string[] vowels1 = { "a", "e", "i", "o", "u" };
    
    
    // If you use explicit instantiation, you can use var.
    var vowels2 = new string[] { "a", "e", "i", "o", "u" };
    
    // If you specify an array size, you must initialize the elements one at a time.
    var vowels3 = new string[5];
    vowels3[0] = "a";
    vowels3[1] = "e";
    // And so on.
    

Delegates

  • Use the concise syntax to create instances of a delegate type.

    C#
    // First, in class Program, define the delegate type and a method that  
    // has a matching signature.
    
    // Define the type.
    public delegate void Del(string message);
    
    // Define a method that has a matching signature.
    public static void DelMethod(string str)
    {
        Console.WriteLine("DelMethod argument: {0}", str);
    }
    
    C#
    // In the Main method, create an instance of Del.
    
    // Preferred: Create an instance of Del by using condensed syntax.
    Del exampleDel2 = DelMethod;
    
    // The following declaration uses the full syntax.
    Del exampleDel1 = new Del(DelMethod);
    

try-catch and using Statements in Exception Handling

  • Use a try-catch statement for most exception handling.

    C#
    static string GetValueFromArray(string[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch (System.IndexOutOfRangeException ex)
        {
            Console.WriteLine("Index is out of range: {0}", index);
            throw;
        }
    }
    
  • Simplify your code by using the C# using statement. If you have a try-finally statement in which the only code in the finally block is a call to the Dispose method, use a using statement instead.

    C#
    // This try-finally statement only calls Dispose in the finally block.
    Font font1 = new Font("Arial", 10.0f);
    try
    {
        byte charset = font1.GdiCharSet;
    }
    finally
    {
        if (font1 != null)
        {
            ((IDisposable)font1).Dispose();
        }
    }
    
    
    // You can do the same thing with a using statement.
    using (Font font2 = new Font("Arial", 10.0f))
    {
        byte charset = font2.GdiCharSet;
    }
    

&& and || Operators

  • To avoid exceptions and increase performance by skipping unnecessary comparisons, use && instead of & and || instead of | when you perform comparisons, as shown in the following example.

    C#
    Console.Write("Enter a dividend: ");
    var dividend = Convert.ToInt32(Console.ReadLine());
    
    Console.Write("Enter a divisor: ");
    var divisor = Convert.ToInt32(Console.ReadLine());
    
    // If the divisor is 0, the second clause in the following condition
    // causes a run-time error. The && operator short circuits when the
    // first expression is false. That is, it does not evaluate the
    // second expression. The & operator evaluates both, and causes 
    // a run-time error when divisor is 0.
    if ((divisor != 0) && (dividend / divisor > 0))
    {
        Console.WriteLine("Quotient: {0}", dividend / divisor);
    }
    else
    {
        Console.WriteLine("Attempted division by 0 ends up here.");
    }
    

New Operator

  • Use the concise form of object instantiation, with implicit typing, as shown in the following declaration.

    C#
    var instance1 = new ExampleClass();
    

    The previous line is equivalent to the following declaration.

    C#
    ExampleClass instance2 = new ExampleClass();
    
  • Use object initializers to simplify object creation.

    C#
    // Object initializer.
    var instance3 = new ExampleClass { Name = "Desktop", ID = 37414, 
        Location = "Redmond", Age = 2.3 };
    
    // Default constructor and assignment statements.
    var instance4 = new ExampleClass();
    instance4.Name = "Desktop";
    instance4.ID = 37414;
    instance4.Location = "Redmond";
    instance4.Age = 2.3;
    

Event Handling

  • If you are defining an event handler that you do not need to remove later, use a lambda expression.

    C#
    public Form2()
    {
        // You can use a lambda expression to define an event handler.
        this.Click += (s, e) =>
            {
                MessageBox.Show(
                    ((MouseEventArgs)e).Location.ToString());
            };
    }
    
    C#
    // Using a lambda expression shortens the following traditional definition.
    public Form1()
    {
        this.Click += new EventHandler(Form1_Click);
    }
    
    void Form1_Click(object sender, EventArgs e)
    {
        MessageBox.Show(((MouseEventArgs)e).Location.ToString());
    }
    

Static Members

  • Call static members by using the class name: ClassName.StaticMember. This practice makes code more readable by making static access clear. Do not qualify a static member defined in a base class with the name of a derived class. While that code compiles, the code readability is misleading, and the code may break in the future if you add a static member with the same name to the derived class.

LINQ Queries

  • Use meaningful names for query variables. The following example uses seattleCustomers for customers who are located in Seattle.

    C#
    var seattleCustomers = from cust in customers
                           where cust.City == "Seattle"
                           select cust.Name;
    
  • Use aliases to make sure that property names of anonymous types are correctly capitalized, using Pascal casing.

    C#
    var localDistributors =
        from customer in customers
        join distributor in distributors on customer.City equals distributor.City
        select new { Customer = customer, Distributor = distributor };
    
  • Rename properties when the property names in the result would be ambiguous. For example, if your query returns a customer name and a distributor ID, instead of leaving them as Name and ID in the result, rename them to clarify that Name is the name of a customer, and ID is the ID of a distributor.

    C#
    var localDistributors2 =
        from cust in customers
        join dist in distributors on cust.City equals dist.City
        select new { CustomerName = cust.Name, DistributorID = dist.ID };
    
  • Use implicit typing in the declaration of query variables and range variables.

    C#
    var seattleCustomers = from cust in customers
                           where cust.City == "Seattle"
                           select cust.Name;
    
  • Align query clauses under the from clause, as shown in the previous examples.

  • Use where clauses before other query clauses to ensure that later query clauses operate on the reduced, filtered set of data.

    C#
    var seattleCustomers2 = from cust in customers
                            where cust.City == "Seattle"
                            orderby cust.Name
                            select cust;
    
  • Use multiple from clauses instead of a join clause to access inner collections. For example, a collection of Student objects might each contain a collection of test scores. When the following query is executed, it returns each score that is over 90, along with the last name of the student who received the score.

    C#
    // Use a compound from to access the inner sequence within each element.
    var scoreQuery = from student in students
                     from score in student.Scores
                     where score > 90
                     select new { Last = student.LastName, score };









'프로그래밍 > C#' 카테고리의 다른 글

C# Programming Guide - Strings  (0) 2017.05.10
C# Programming Guide - Types  (0) 2017.05.10
C# Programming Guide - Statements, Expressions, and Operators  (0) 2017.05.10
C# Programming Guide - Arrays  (0) 2017.05.09
Tuple Class  (0) 2017.05.08
:
Posted by 지훈2


  1. Statements, Expressions, and Operators
  2. Statements
  3. Expressions
  4. Operators
  5. Anonymous Functions
  6. Anonymous Functions - Lambda Expressions
  7. How to: Use Lambda Expressions in a Query
  8. How to: Use Lambda Expressions Outside LINQ
  9. Anonymous Methods
  10. Overloadable Operators
  11. Conversion Operators
  12. Using Conversion Operators
  13. How to: Implement User-Defined Conversions Between Structs
  14. How to: Use Operator Overloading to Create a Complex Number Class
  15. Equality Comparisons
  16. How to: Define Value Equality for a Type
  17. How to: Test for Reference Equality



https://docs.microsoft.com/en-us/dotnet/articles/csharp/programming-guide/statements-expressions-operators/



Statements, Expressions, and Operators


The C# code that comprises an application consists of statements made up of keywords, expressions and operators. This section contains information regarding these fundamental elements of a C# program.

For more information, see:






Statements


The actions that a program takes are expressed in statements. Common actions include declaring variables, assigning values, calling methods, looping through collections, and branching to one or another block of code, depending on a given condition. The order in which statements are executed in a program is called the flow of control or flow of execution. The flow of control may vary every time that a program is run, depending on how the program reacts to input that it receives at run time.

A statement can consist of a single line of code that ends in a semicolon, or a series of single-line statements in a block. A statement block is enclosed in {} brackets and can contain nested blocks. The following code shows two examples of single-line statements, and a multi-line statement block:

C#
    static void Main()
    {
        // Declaration statement.
        int counter;

        // Assignment statement.
        counter = 1;

        // Error! This is an expression, not an expression statement.
        // counter + 1; 

        // Declaration statements with initializers are functionally
        // equivalent to  declaration statement followed by assignment statement:         
        int[] radii = { 15, 32, 108, 74, 9 }; // Declare and initialize an array.
        const double pi = 3.14159; // Declare and initialize  constant.          

        // foreach statement block that contains multiple statements.
        foreach (int radius in radii)
        {
            // Declaration statement with initializer.
            double circumference = pi * (2 * radius);

            // Expression statement (method invocation). A single-line
            // statement can span multiple text lines because line breaks
            // are treated as white space, which is ignored by the compiler.
            System.Console.WriteLine("Radius of circle #{0} is {1}. Circumference = {2:N2}",
                                    counter, radius, circumference);

            // Expression statement (postfix increment).
            counter++;

        } // End of foreach statement block
    } // End of Main method body.
} // End of SimpleStatements class.
/*
   Output:
    Radius of circle #1 = 15. Circumference = 94.25
    Radius of circle #2 = 32. Circumference = 201.06
    Radius of circle #3 = 108. Circumference = 678.58
    Radius of circle #4 = 74. Circumference = 464.96
    Radius of circle #5 = 9. Circumference = 56.55
*/

Types of Statements

The following table lists the various types of statements in C# and their associated keywords, with links to topics that include more information:

CategoryC# keywords / notes
Declaration statementsA declaration statement introduces a new variable or constant. A variable declaration can optionally assign a value to the variable. In a constant declaration, the assignment is required.

C#
// Variable declaration statements.
double area;
double radius = 2;

// Constant declaration statement.
const double pi = 3.14159;
Expression statementsExpression statements that calculate a value must store the value in a variable.

C#
// Expression statement (assignment).
area = 3.14 * (radius * radius);

// Error. Not  statement because no assignment:
//circ * 2;

// Expression statement (method invocation).
System.Console.WriteLine();

// Expression statement (new object creation).
System.Collections.Generic.List<string> strings =
    new System.Collections.Generic.List<string>();
Selection statementsSelection statements enable you to branch to different sections of code, depending on one or more specified conditions. For more information, see the following topics:

ifelseswitchcase
Iteration statementsIteration statements enable you to loop through collections like arrays, or perform the same set of statements repeatedly until a specified condition is met. For more information, see the following topics:

doforforeachinwhile
Jump statementsJump statements transfer control to another section of code. For more information, see the following topics:

breakcontinuedefaultgotoreturnyield
Exception handling statementsException handling statements enable you to gracefully recover from exceptional conditions that occur at run time. For more information, see the following topics:

throwtry-catchtry-finallytry-catch-finally
Checked and uncheckedChecked and unchecked statements enable you to specify whether numerical operations are allowed to cause an overflow when the result is stored in a variable that is too small to hold the resulting value. For more information, see checked and unchecked.
he await statementIf you mark a method with the async modifier, you can use the await operator in the method. When control reaches an await expression in the async method, control returns to the caller, and progress in the method is suspended until the awaited task completes. When the task is complete, execution can resume in the method.

For a simple example, see the "Async Methods" section of Methods. For more information, see Asynchronous Programming with async and await.
The yield return statementAn iterator performs a custom iteration over a collection, such as a list or an array. An iterator uses the yield return statement to return each element one at a time. When a yield return statement is reached, the current location in code is remembered. Execution is restarted from that location when the iterator is called the next time.

For more information, see Iterators.
The fixed statementThe fixed statement prevents the garbage collector from relocating a movable variable. For more information, see fixed.
The lock statementThe lock statement enables you to limit access to blocks of code to only one thread at a time. For more information, see lock.
Labeled statementsYou can give a statement a label and then use the goto keyword to jump to the labeled statement. (See the example in the following row.)
The empty statementThe empty statement consists of a single semicolon. It does nothing and can be used in places where a statement is required but no action needs to be performed. The following examples show two uses for an empty statement:

C#
void ProcessMessages()
{
    while (ProcessMessage())
        ; // Statement needed here.
}

void F()
{
    //...
    if (done) goto exit;
//...
exit:
    ; // Statement needed here.
}

Embedded Statements

Some statements, including dowhilefor, and foreach, always have an embedded statement that follows them. This embedded statement may be either a single statement or multiple statements enclosed by {} brackets in a statement block. Even single-line embedded statements can be enclosed in {} brackets, as shown in the following example:

C#
// Recommended style. Embedded statement in  block.
foreach (string s in System.IO.Directory.GetDirectories(
                        System.Environment.CurrentDirectory))
{
    System.Console.WriteLine(s);
}

// Not recommended.
foreach (string s in System.IO.Directory.GetDirectories(
                        System.Environment.CurrentDirectory))
    System.Console.WriteLine(s);

An embedded statement that is not enclosed in {} brackets cannot be a declaration statement or a labeled statement. This is shown in the following example:

C#
if(pointB == true)
    //Error CS1023:
    int radius = 5; 

Put the embedded statement in a block to fix the error:

C#
if (b == true)
{
    // OK:
    System.DateTime d = System.DateTime.Now;
    System.Console.WriteLine(d.ToLongDateString());
}

Nested Statement Blocks

Statement blocks can be nested, as shown in the following code:

C#
foreach (string s in System.IO.Directory.GetDirectories(
    System.Environment.CurrentDirectory))
{
    if (s.StartsWith("CSharp"))
    {
        if (s.EndsWith("TempFolder"))
        {
            return s;
        }
    }

}
return "Not found.";

Unreachable Statements

If the compiler determines that the flow of control can never reach a particular statement under any circumstances, it will produce warning CS0162, as shown in the following example:

C#
// An over-simplified example of unreachable code.
const int val = 5;
if (val < 4)
{
    System.Console.WriteLine("I'll never write anything."); //CS0162
}






Expressions


An expression is a sequence of one or more operands and zero or more operators that can be evaluated to a single value, object, method, or namespace. Expressions can consist of a literal value, a method invocation, an operator and its operands, or a simple name. Simple names can be the name of a variable, type member, method parameter, namespace or type.

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

Code
((x < 10) && ( x > 5)) || ((x > 20) && (x < 25))   
System.Convert.ToInt32("35")  

Expression Values

In most of the contexts in which expressions are used, for example in statements or method parameters, the expression is expected to evaluate to some value. If x and y are integers, the expression x + y evaluates to a numeric value. The expression new MyClass() evaluates to a reference to a new instance of a MyClass object. The expression myClass.ToString() evaluates to a string because that is the return type of the method. However, although a namespace name is classified as an expression, it does not evaluate to a value and therefore can never be the final result of any expression. You cannot pass a namespace name to a method parameter, or use it in a new expression, or assign it to a variable. You can only use it as a sub-expression in a larger expression. The same is true for types (as distinct from System.Type objects), method group names (as distinct from specific methods), and event add and remove accessors.

Every value has an associated type. For example, if x and y are both variables of type int, the value of the expression x + y is also typed as int. If the value is assigned to a variable of a different type, or if x and y are different types, the rules of type conversion are applied. For more information about how such conversions work, see Casting and Type Conversions.

Overflows

Numeric expressions may cause overflows if the value is larger than the maximum value of the value's type. For more information, see Checked and Unchecked and Explicit Numeric Conversions Table.

Operator Precedence and Associativity

The manner in which an expression is evaluated is governed by the rules of associativity and operator precedence. For more information, see Operators.

Most expressions, except assignment expressions and method invocation expressions, must be embedded in a statement. For more information, see Statements.

Literals and Simple Names

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

C#
// Expression statements.
int i = 5;
string s = "Hello World";

For more information on literals, see Types.

In the preceding example, both i and s are simple names that identify local variables. When those variables are used in an expression, the variable name evaluates to the value that is currently stored in the variable's location in memory. This is shown in the following example:

C#
int num = 5;
System.Console.WriteLine(num); // Output: 5
num = 6;
System.Console.WriteLine(num); // Output: 6            

Invocation Expressions

In the following code example, the call to DoWork is an invocation expression.

Code
DoWork();  

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

Query Expressions

The same rules for expressions in general apply to query expressions. For more information, see LINQ Query Expressions.

Lambda Expressions

Lambda expressions represent "inline methods" that have no name but can have input parameters and multiple statements. They are used extensively in LINQ to pass arguments to methods. Lambda expressions are compiled to either delegates or expression trees depending on the context in which they are used. For more information, see Lambda Expressions.

Expression Trees

Expression trees enable expressions to be represented as data structures. They are used extensively by LINQ providers to translate query expressions into code that is meaningful in some other context, such as a SQL database. For more information, see Expression Trees.

Remarks

Whenever a variable, object property, or object indexer access is identified from an expression, the value of that item is used as the value of the expression. An expression can be placed anywhere in C# where a value or object is required, as long as the expression ultimately evaluates to the required type.






Operators


In C#, an operator is a program element that is applied to one or more operands in an expression or statement. Operators that take one operand, such as the increment operator (++) or new, are referred to as unary operators. Operators that take two operands, such as arithmetic operators (+,-,*,/), are referred to as binary operators. One operator, the conditional operator (?:), takes three operands and is the sole ternary operator in C#.

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

C#
y++;

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

C#
y = 2 + 3;

Operators, Evaluation, and Operator Precedence

An operand can be a valid expression that is composed of any length of code, and it can comprise any number of sub expressions. In an expression that contains multiple operators, the order in which the operators are applied is determined by operator precedenceassociativity, and parentheses.

Each operator has a defined precedence. In an expression that contains multiple operators that have different precedence levels, the precedence of the operators determines the order in which the operators are evaluated. For example, the following statement assigns 3 to n1.

n1 = 11 - 2 * 4;

The multiplication is executed first because multiplication takes precedence over subtraction.

The following table separates the operators into categories based on the type of operation they perform. The categories are listed in order of precedence.

Primary Operators

ExpressionDescription
x.y

x?.y
Member access

Conditional member access
f(x)Method and delegate invocation
a[x]

a?[x]
Array and indexer access

Conditional array and indexer access
x++Post-increment
x--Post-decrement
new T(...)Object and delegate creation
new T(...){...}Object creation with initializer. See Object and Collection Initializers.
new {...}Anonymous object initializer. See Anonymous Types.
new T[...]Array creation. See Arrays.
typeof(T)Obtain System.Type object for T
checked(x)Evaluate expression in checked context
unchecked(x)Evaluate expression in unchecked context
default (T)Obtain default value of type T
delegate {}Anonymous function (anonymous method)

Unary Operators

ExpressionDescription
+xIdentity
-xNegation
!xLogical negation
~xBitwise negation
++xPre-increment
--xPre-decrement
(T)xExplicitly convert x to type T

Multiplicative Operators

ExpressionDescription
*Multiplication
/Division
%Remainder

Additive Operators

ExpressionDescription
+ yAddition, string concatenation, delegate combination
- ySubtraction, delegate removal

Shift Operators

ExpressionDescription
<< yShift left
>> yShift right

Relational and Type Operators

ExpressionDescription
< yLess than
> yGreater than
<= yLess than or equal
>= yGreater than or equal
is TReturn true if x is a T, false otherwise
as TReturn x typed as T, or null if x is not a T

Equality Operators

ExpressionDescription
== yEqual
!= yNot equal

Logical, Conditional, and Null Operators

CategoryExpressionDescription
Logical AND& yInteger bitwise AND, Boolean logical AND
Logical XOR^ yInteger bitwise XOR, boolean logical XOR
Logical OR| yInteger bitwise OR, boolean logical OR
Conditional AND&& yEvaluates y only if x is true
Conditional OR|| yEvaluates y only if x is false
Null coalescing?? yEvaluates to y if x is null, to x otherwise
Conditional? y : zEvaluates to y if x is true, z if x is false

Assignment and Anonymous Operators

ExpressionDescription
=Assignment
x op= yCompound assignment. Supports these operators: +=-=*=/=%=&=|=!=<<=>>=
(T x) => yAnonymous function (lambda expression)

Associativity

When two or more operators that have the same precedence are present in an expression, they are evaluated based on associativity. Left-associative operators are evaluated in order from left to right. For example, x * y / z is evaluated as (x * y) / z. Right-associative operators are evaluated in order from right to left. For example, the assignment operator is right associative. If it were not, the following code would result in an error.

C#
int a, b, c;  
c = 1;  
// The following two lines are equivalent.  
a = b = c;  
a = (b = c);  

// The following line, which forces left associativity, causes an error.  
//(a = b) = c;  

As another example the ternary operator (?:) is right associative. Most binary operators are left associative.

Whether the operators in an expression are left associative or right associative, the operands of each expression are evaluated first, from left to right. The following examples illustrate the order of evaluation of operators and operands.

StatementOrder of evaluation
a = ba, b, =
a = b + ca, b, c, +, =
a = b + c * da, b, c, d, *, +, =
a = b * c + da, b, c, *, d, +, =
a = b - c + da, b, c, -, d, +, =
a += b -= ca, b, c, -=, +=

Adding Parentheses

You can change the order imposed by operator precedence and associativity by using parentheses. For example, 2 + 3 * 2 ordinarily evaluates to 8, because multiplicative operators take precedence over additive operators. However, if you write the expression as (2 + 3) * 2, the addition is evaluated before the multiplication, and the result is 10. The following examples illustrate the order of evaluation in parenthesized expressions. As in previous examples, the operands are evaluated before the operator is applied.

StatementOrder of evaluation
a = (b + c) * da, b, c, +, d, *, =
a = b - (c + d)a, b, c, d, +, -, =
a = (b + c) * (d - e)a, b, c, +, d, e, -, *, =

Operator Overloading

You can change the behavior of operators for custom classes and structs. This process is referred to as operator overloading. For more information, see Overloadable Operators.

For more information, see Operator Keywords and C# Operators.






Anonymous Functions


An anonymous function is an "inline" statement or expression that can be used wherever a delegate type is expected. You can use it to initialize a named delegate or pass it instead of a named delegate type as a method parameter.

There are two kinds of anonymous functions, which are discussed individually in the following topics:

The Evolution of Delegates in C#

In C# 1.0, you created an instance of a delegate by explicitly initializing it with a method that was defined elsewhere in the code. C# 2.0 introduced the concept of anonymous methods as a way to write unnamed inline statement blocks that can be executed in a delegate invocation. C# 3.0 introduced lambda expressions, which are similar in concept to anonymous methods but more expressive and concise. These two features are known collectively as anonymous functions. In general, applications that target version 3.5 and later of the .NET Framework should use lambda expressions.

The following example demonstrates the evolution of delegate creation from C# 1.0 to C# 3.0:

C#
class Test
{
    delegate void TestDelegate(string s);
    static void M(string s)
    {
        Console.WriteLine(s);
    }

    static void Main(string[] args)
    {
        // Original delegate syntax required 
        // initialization with a named method.
        TestDelegate testDelA = new TestDelegate(M);

        // C# 2.0: A delegate can be initialized with
        // inline code, called an "anonymous method." This
        // method takes a string as an input parameter.
        TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); };

        // C# 3.0. A delegate can be initialized with
        // a lambda expression. The lambda also takes a string
        // as an input parameter (x). The type of x is inferred by the compiler.
        TestDelegate testDelC = (x) => { Console.WriteLine(x); };

        // Invoke the delegates.
        testDelA("Hello. My name is M and I write lines.");
        testDelB("That's nothing. I'm anonymous and ");
        testDelC("I'm a famous author.");

        // Keep console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
    Hello. My name is M and I write lines.
    That's nothing. I'm anonymous and
    I'm a famous author.
    Press any key to exit.
 */






Anonymous Functions - Lambda Expressions


A lambda expression is an anonymous function that you can use to create delegates or expression tree types. By using lambda expressions, you can write local functions that can be passed as arguments or returned as the value of function calls. Lambda expressions are particularly helpful for writing LINQ query expressions.

To create a lambda expression, you specify input parameters (if any) on the left side of the lambda operator =>, and you put the expression or statement block on the other side. For example, the lambda expression x => x * x specifies a parameter that’s named x and returns the value of x squared. You can assign this expression to a delegate type, as the following example shows:

C#
delegate int del(int i);  
static void Main(string[] args)  
{  
    del myDelegate = x => x * x;  
    int j = myDelegate(5); //j = 25  
}  

To create an expression tree type:

C#
using System.Linq.Expressions;  

namespace ConsoleApplication1  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            Expression<del> myET = x => x * x;  
        }  
    }  
}  

The => operator has the same precedence as assignment (=) and is right associative (see “Associativity” section of the Operators article).

Lambdas are used in method-based LINQ queries as arguments to standard query operator methods such as Where.

When you use method-based syntax to call the Where method in the Enumerable class (as you do in LINQ to Objects and LINQ to XML) the parameter is a delegate type System.Func<T,TResult>. A lambda expression is the most convenient way to create that delegate. When you call the same method in, for example, the System.Linq.Queryable class (as you do in LINQ to SQL) then the parameter type is an System.Linq.Expressions.Expression where Func is any of the Func delegates with up to sixteen input parameters. Again, a lambda expression is just a very concise way to construct that expression tree. The lambdas allow the Where calls to look similar although in fact the type of object created from the lambda is different.

In the previous example, notice that the delegate signature has one implicitly-typed input parameter of type int, and returns an int. The lambda expression can be converted to a delegate of that type because it also has one input parameter (x) and a return value that the compiler can implicitly convert to type int. (Type inference is discussed in more detail in the following sections.) When the delegate is invoked by using an input parameter of 5, it returns a result of 25.

Lambdas are not allowed on the left side of the is or as operator.

All restrictions that apply to anonymous methods also apply to lambda expressions. For more information, see Anonymous Methods.

Expression Lambdas

A lambda expression with an expression on the right side of the => operator is called an expression lambda. Expression lambdas are used extensively in the construction of Expression Trees. An expression lambda returns the result of the expression and takes the following basic form:

C#
(input-parameters) => expression

The parentheses are optional only if the lambda has one input parameter; otherwise they are required. Two or more input parameters are separated by commas enclosed in parentheses:

C#
(x, y) => x == y

Sometimes it is difficult or impossible for the compiler to infer the input types. When this occurs, you can specify the types explicitly as shown in the following example:

C#
(int x, string s) => s.Length > x

Specify zero input parameters with empty parentheses:

C#
() => SomeMethod()

Note in the previous example that the body of an expression lambda can consist of a method call. However, if you are creating expression trees that are evaluated outside of the .NET Framework, such as in SQL Server, you should not use method calls in lambda expressions. The methods will have no meaning outside the context of the .NET common language runtime.

Statement Lambdas

A statement lambda resembles an expression lambda except that the statement(s) is enclosed in braces:

(input-parameters) => { statement; }

The body of a statement lambda can consist of any number of statements; however, in practice there are typically no more than two or three.

C#
delegate void TestDelegate(string s);
C#
TestDelegate del = n => { string s = n + " World"; 
                          Console.WriteLine(s); };

Statement lambdas, like anonymous methods, cannot be used to create expression trees.

Async Lambdas

You can easily create lambda expressions and statements that incorporate asynchronous processing by using the async and await keywords. For example, the following Windows Forms example contains an event handler that calls and awaits an async method, ExampleMethodAsync.

C#
public partial class Form1 : Form  
{  
    public Form1()  
    {  
        InitializeComponent();  
    }  

    private async void button1_Click(object sender, EventArgs e)  
    {  
        // ExampleMethodAsync returns a Task.  
        await ExampleMethodAsync();  
        textBox1.Text += "\r\nControl returned to Click event handler.\n";  
    }  

    async Task ExampleMethodAsync()  
    {  
        // The following line simulates a task-returning asynchronous process.  
        await Task.Delay(1000);  
    }  
}  

You can add the same event handler by using an async lambda. To add this handler, add an async modifier before the lambda parameter list, as the following example shows.

C#
public partial class Form1 : Form  
{  
    public Form1()  
    {  
        InitializeComponent();  
        button1.Click += async (sender, e) =>  
        {  
            // ExampleMethodAsync returns a Task.  
            await ExampleMethodAsync();  
            textBox1.Text += "\nControl returned to Click event handler.\n";  
        };  
    }  

    async Task ExampleMethodAsync()  
    {  
        // The following line simulates a task-returning asynchronous process.  
        await Task.Delay(1000);  
    }  
}  

For more information about how to create and use async methods, see Asynchronous Programming with async and await.

Lambdas with the Standard Query Operators

Many Standard query operators have an input parameter whose type is one of the Func<T,TResult> family of generic delegates. These delegates use type parameters to define the number and types of input parameters, and the return type of the delegate. Func delegates are very useful for encapsulating user-defined expressions that are applied to each element in a set of source data. For example, consider the following delegate type:

C#
public delegate TResult Func<TArg0, TResult>(TArg0 arg0)  

The delegate can be instantiated as Func<int,bool> myFunc where int is an input parameter and bool is the return value. The return value is always specified in the last type parameter. Func<int, string, bool> defines a delegate with two input parameters, int and string, and a return type of bool. The following Func delegate, when it is invoked, will return true or false to indicate whether the input parameter is equal to 5:

C#
Func<int, bool> myFunc = x => x == 5;  
bool result = myFunc(4); // returns false of course  

You can also supply a lambda expression when the argument type is an Expression<Func>, for example in the standard query operators that are defined in System.Linq.Queryable. When you specify an Expression<Func> argument, the lambda will be compiled to an expression tree.

A standard query operator, the Count method, is shown here:

C#
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };  
int oddNumbers = numbers.Count(n => n % 2 == 1);  

The compiler can infer the type of the input parameter, or you can also specify it explicitly. This particular lambda expression counts those integers (n) which when divided by two have a remainder of 1.

The following line of code produces a sequence that contains all elements in the numbers array that are to the left side of the 9 because that's the first number in the sequence that doesn't meet the condition:

C#
var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);  

This example shows how to specify multiple input parameters by enclosing them in parentheses. The method returns all the elements in the numbers array until a number is encountered whose value is less than its position. Do not confuse the lambda operator (=>) with the greater than or equal operator (>=).

C#
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);  

Type Inference in Lambdas

When writing lambdas, you often do not have to specify a type for the input parameters because the compiler can infer the type based on the lambda body, the parameter’s delegate type, and other factors as described in the C# Language Specification. For most of the standard query operators, the first input is the type of the elements in the source sequence. So if you are querying an IEnumerable<Customer>, then the input variable is inferred to be a Customer object, which means you have access to its methods and properties:

C#
customers.Where(c => c.City == "London");  

The general rules for lambdas are as follows:

  • The lambda must contain the same number of parameters as the delegate type.

  • Each input parameter in the lambda must be implicitly convertible to its corresponding delegate parameter.

  • The return value of the lambda (if any) must be implicitly convertible to the delegate's return type.

Note that lambda expressions in themselves do not have a type because the common type system has no intrinsic concept of "lambda expression." However, it is sometimes convenient to speak informally of the "type" of a lambda expression. In these cases the type refers to the delegate type or Expression type to which the lambda expression is converted.

Variable Scope in Lambda Expressions

Lambdas can refer to outer variables (see Anonymous Methods) that are in scope in the method that defines the lambda function, or in scope in the type that contains the lambda expression. Variables that are captured in this manner are stored for use in the lambda expression even if the variables would otherwise go out of scope and be garbage collected. An outer variable must be definitely assigned before it can be consumed in a lambda expression. The following example demonstrates these rules:

C#
delegate bool D();  
delegate bool D2(int i);  

class Test  
{  
    D del;  
    D2 del2;  
    public void TestMethod(int input)  
    {  
        int j = 0;  
        // Initialize the delegates with lambda expressions.  
        // Note access to 2 outer variables.  
        // del will be invoked within this method.  
        del = () => { j = 10;  return j > input; };  

        // del2 will be invoked after TestMethod goes out of scope.  
        del2 = (x) => {return x == j; };  

        // Demonstrate value of j:  
        // Output: j = 0   
        // The delegate has not been invoked yet.  
        Console.WriteLine("j = {0}", j);        // Invoke the delegate.  
        bool boolResult = del();  

        // Output: j = 10 b = True  
        Console.WriteLine("j = {0}. b = {1}", j, boolResult);  
    }  

    static void Main()  
    {  
        Test test = new Test();  
        test.TestMethod(5);  

        // Prove that del2 still has a copy of  
        // local variable j from TestMethod.  
        bool result = test.del2(10);  

        // Output: True  
        Console.WriteLine(result);  

        Console.ReadKey();  
    }  
}  

The following rules apply to variable scope in lambda expressions:

  • A variable that is captured will not be garbage-collected until the delegate that references it becomes eligible for garbage collection.

  • Variables introduced within a lambda expression are not visible in the outer method.

  • A lambda expression cannot directly capture a ref or out parameter from an enclosing method.

  • A return statement in a lambda expression does not cause the enclosing method to return.

  • A lambda expression cannot contain a goto statement, break statement, or continue statement that is inside the lambda function if the jump statement’s target is outside the block. It is also an error to have a jump statement outside the lambda function block if the target is inside the block.






How to: Use Lambda Expressions in a Query


You do not use lambda expressions directly in query syntax, but you do use them in method calls, and query expressions can contain method calls. In fact, some query operations can only be expressed in method syntax. For more information about the difference between query syntax and method syntax, see Query Syntax and Method Syntax in LINQ.

Example

The following example demonstrates how to use a lambda expression in a method-based query by using the Enumerable.Where standard query operator. Note that the Where method in this example has an input parameter of the delegate type Func<TResult> and that delegate takes an integer as input and returns a Boolean. The lambda expression can be converted to that delegate. If this were a LINQ to SQL query that used the Queryable.Where method, the parameter type would be an Expression<Func\<int,bool>> but the lambda expression would look exactly the same. For more information on the Expression type, see System.Linq.Expressions.Expression.

C#
class SimpleLambda
{
    static void Main()
    {

        // Data source.
        int[] scores = { 90, 71, 82, 93, 75, 82 };

        // The call to Count forces iteration of the source
        int highScoreCount = scores.Where(n => n > 80).Count();

        Console.WriteLine("{0} scores are greater than 80", highScoreCount);

        // Outputs: 4 scores are greater than 80            
    }
}

Example

The following example demonstrates how to use a lambda expression in a method call of a query expression. The lambda is necessary because the Sum standard query operator cannot be invoked by using query syntax.

The query first groups the students according to their grade level, as defined in the GradeLevel enum. Then for each group it adds the total scores for each student. This requires two Sum operations. The inner Sum calculates the total score for each student, and the outer Sum keeps a running, combined total for all students in the group.

C#
private static void TotalsByGradeLevel()
{
    // This query retrieves the total scores for First Year students, Second Years, and so on.
    // The outer Sum method uses a lambda in order to specify which numbers to add together.
    var categories =
    from student in students
    group student by student.Year into studentGroup
    select new { GradeLevel = studentGroup.Key, TotalScore = studentGroup.Sum(s => s.ExamScores.Sum()) };

    // Execute the query.   
    foreach (var cat in categories)
    {
        Console.WriteLine("Key = {0} Sum = {1}", cat.GradeLevel, cat.TotalScore);
    }
}
/*
     Outputs: 
     Key = SecondYear Sum = 1014
     Key = ThirdYear Sum = 964
     Key = FirstYear Sum = 1058
     Key = FourthYear Sum = 974
*/






How to: Use Lambda Expressions Outside LINQ


Lambda expressions are not limited to LINQ queries. You can use them anywhere a delegate value is expected, that is, wherever an anonymous method can be used. The following example shows how to use a lambda expression in a Windows Forms event handler. Notice that the types of the inputs (Object and MouseEventArgs) are inferred by the compiler and do not have to be explicitly given in the lambda input parameters.

Example

Code
public partial class Form1 : Form  
{  
    public Form1()  
    {  
        InitializeComponent();  
        // Use a lambda expression to define an event handler.  
       this.Click += (s, e) => { MessageBox.Show(((MouseEventArgs)e).Location.ToString());};  
    }  
}  






Anonymous Methods


In versions of C# before 2.0, the only way to declare a delegate was to use named methods. C# 2.0 introduced anonymous methods and in C# 3.0 and later, lambda expressions supersede anonymous methods as the preferred way to write inline code. However, the information about anonymous methods in this topic also applies to lambda expressions. There is one case in which an anonymous method provides functionality not found in lambda expressions. Anonymous methods enable you to omit the parameter list. This means that an anonymous method can be converted to delegates with a variety of signatures. This is not possible with lambda expressions. For more information specifically about lambda expressions, see Lambda Expressions.

Creating anonymous methods is essentially a way to pass a code block as a delegate parameter. Here are two examples:

C#
// Create a handler for a click event.
button1.Click += delegate(System.Object o, System.EventArgs e)
                   { System.Windows.Forms.MessageBox.Show("Click!"); };
C#
// Create a delegate.
delegate void Del(int x);

// Instantiate the delegate using an anonymous method.
Del d = delegate(int k) { /* ... */ };

By using anonymous methods, you reduce the coding overhead in instantiating delegates because you do not have to create a separate method.

For example, specifying a code block instead of a delegate can be useful in a situation when having to create a method might seem an unnecessary overhead. A good example would be when you start a new thread. This class creates a thread and also contains the code that the thread executes without creating an additional method for the delegate.

C#
void StartThread()
{
    System.Threading.Thread t1 = new System.Threading.Thread
      (delegate()
            {
                System.Console.Write("Hello, ");
                System.Console.WriteLine("World!");
            });
    t1.Start();
}

Remarks

The scope of the parameters of an anonymous method is the anonymous-method-block.

It is an error to have a jump statement, such as gotobreak, or continue, inside the anonymous method block if the target is outside the block. It is also an error to have a jump statement, such as gotobreak, or continue, outside the anonymous method block if the target is inside the block.

The local variables and parameters whose scope contains an anonymous method declaration are called outer variables of the anonymous method. For example, in the following code segment, n is an outer variable:

C#
int n = 0;
Del d = delegate() { System.Console.WriteLine("Copy #:{0}", ++n); };

A reference to the outer variable n is said to be captured when the delegate is created. Unlike local variables, the lifetime of a captured variable extends until the delegates that reference the anonymous methods are eligible for garbage collection.

An anonymous method cannot access the ref or out parameters of an outer scope.

No unsafe code can be accessed within the anonymous-method-block.

Anonymous methods are not allowed on the left side of the is operator.

Example

The following example demonstrates two ways of instantiating a delegate:

  • Associating the delegate with an anonymous method.

  • Associating the delegate with a named method (DoWork).

In each case, a message is displayed when the delegate is invoked.

C#
// Declare a delegate.
delegate void Printer(string s);

class TestClass
{
    static void Main()
    {
        // Instantiate the delegate type using an anonymous method.
        Printer p = delegate(string j)
        {
            System.Console.WriteLine(j);
        };

        // Results from the anonymous delegate call.
        p("The delegate using the anonymous method is called.");

        // The delegate instantiation using a named method "DoWork".
        p = new Printer(TestClass.DoWork);

        // Results from the old style delegate call.
        p("The delegate using the named method is called.");
    }

    // The method associated with the named delegate.
    static void DoWork(string k)
    {
        System.Console.WriteLine(k);
    }
}
/* Output:
    The delegate using the anonymous method is called.
    The delegate using the named method is called.
*/






Overloadable Operators


C# allows user-defined types to overload operators by defining static member functions using the operatorkeyword. Not all operators can be overloaded, however, and others have restrictions, as listed in this table:

OperatorsOverloadability
+-!~++--truefalseThese unary operators can be overloaded.
+-*/%&|^<<>>These binary operators can be overloaded.
==!=<><=>=The comparison operators can be overloaded (but see the note that follows this table).
&&||The conditional logical operators cannot be overloaded, but they are evaluated using & and &#124;, which can be overloaded.
[]The array indexing operator cannot be overloaded, but you can define indexers.
(T)xThe cast operator cannot be overloaded, but you can define new conversion operators (see explicit and implicit).
+=-=*=/=%=&=|=^=<<=>>=Assignment operators cannot be overloaded, but +=, for example, is evaluated using +, which can be overloaded.
=.?:??->=>f(x)ascheckeduncheckeddefaultdelegateisnewsizeoftypeofThese operators cannot be overloaded.
Note

The comparison operators, if overloaded, must be overloaded in pairs; that is, if == is overloaded, != must also be overloaded. The reverse is also true, and similar for < and >, and for <= and >=.

To overload an operator on a custom class requires creating a method on the class with the correct signature. The method must be named "operator X" where X is the name or symbol of the operator being overloaded. Unary operators have one parameter, and binary operators have two parameters. In each case, one parameter must be the same type as the class or struct that declares the operator.

C#
public static Complex operator +(Complex c1, Complex c2)  
    {  
        Return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);  
    }  

It is common to have definitions that simply return immediately with the result of an expression. There is a syntax shortcut using => for these situations.

C#
public static Complex operator +(Complex c1, Complex c2) =>  
        new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);  

    // Override ToString() to display a complex number   
    // in the traditional format:  
    public override string ToString() => $"{this.real} + {this.imaginary}";  

For more information, see How to: Use Operator Overloading to Create a Complex Number Class.






Conversion Operators


C# enables programmers to declare conversions on classes or structs so that classes or structs can be converted to and/or from other classes or structs, or basic types. Conversions are defined like operators and are named for the type to which they convert. Either the type of the argument to be converted, or the type of the result of the conversion, but not both, must be the containing type.

C#
class SampleClass
{
    public static explicit operator SampleClass(int i)
    {
        SampleClass temp = new SampleClass();
        // code to convert from int to SampleClass...

        return temp;
    }
}

Conversion Operators Overview

Conversion operators have the following properties:

  • Conversions declared as implicit occur automatically when it is required.

  • Conversions declared as explicit require a cast to be called.

  • All conversions must be declared as static.

For more information:






Using Conversion Operators


You can use implicit conversion operators, which are easier to use, or explicit conversion operators, which clearly indicate to anyone reading the code that you're converting a type. This topic demonstrates both types of conversion operator.

Example

This is an example of an explicit conversion operator. This operator converts from the type Byte to a value type called Digit. Because not all bytes can be converted to a digit, the conversion is explicit, meaning that a cast must be used, as shown in the Main method.

C#
struct Digit
{
    byte value;

    public Digit(byte value)  //constructor
    {
        if (value > 9)
        {
            throw new System.ArgumentException();
        }
        this.value = value;
    }

    public static explicit operator Digit(byte b)  // explicit byte to digit conversion operator
    {
        Digit d = new Digit(b);  // explicit conversion

        System.Console.WriteLine("Conversion occurred.");
        return d;
    }
}

class TestExplicitConversion
{
    static void Main()
    {
        try
        {
            byte b = 3;
            Digit d = (Digit)b;  // explicit conversion
        }
        catch (System.Exception e)
        {
            System.Console.WriteLine("{0} Exception caught.", e);
        }
    }
}
// Output: Conversion occurred.

Example

This example demonstrates an implicit conversion operator by defining a conversion operator that undoes what the previous example did: it converts from a value class called Digit to the integral Byte type. Because any digit can be converted to a Byte, there's no need to force users to be explicit about the conversion.

C#
struct Digit
{
    byte value;

    public Digit(byte value)  //constructor
    {
        if (value > 9)
        {
            throw new System.ArgumentException();
        }
        this.value = value;
    }

    public static implicit operator byte(Digit d)  // implicit digit to byte conversion operator
    {
        System.Console.WriteLine("conversion occurred");
        return d.value;  // implicit conversion
    }
}

class TestImplicitConversion
{
    static void Main()
    {
        Digit d = new Digit(3);
        byte b = d;  // implicit conversion -- no cast needed
    }
}
// Output: Conversion occurred.






How to: Implement User-Defined Conversions Between Structs


This example defines two structs, RomanNumeral and BinaryNumeral, and demonstrates conversions between them.

Example

C#
struct RomanNumeral
{
    private int value;

    public RomanNumeral(int value)  //constructor
    {
        this.value = value;
    }

    static public implicit operator RomanNumeral(int value)
    {
        return new RomanNumeral(value);
    }

    static public implicit operator RomanNumeral(BinaryNumeral binary)
    {
        return new RomanNumeral((int)binary);
    }

    static public explicit operator int(RomanNumeral roman)
    {
        return roman.value;
    }

    static public implicit operator string(RomanNumeral roman)
    {
        return ("Conversion to string is not implemented");
    }
}

struct BinaryNumeral
{
    private int value;

    public BinaryNumeral(int value)  //constructor
    {
        this.value = value;
    }

    static public implicit operator BinaryNumeral(int value)
    {
        return new BinaryNumeral(value);
    }

    static public explicit operator int(BinaryNumeral binary)
    {
        return (binary.value);
    }

    static public implicit operator string(BinaryNumeral binary)
    {
        return ("Conversion to string is not implemented");
    }
}

class TestConversions
{
    static void Main()
    {
        RomanNumeral roman;
        BinaryNumeral binary;

        roman = 10;

        // Perform a conversion from a RomanNumeral to a BinaryNumeral:
        binary = (BinaryNumeral)(int)roman;

        // Perform a conversion from a BinaryNumeral to a RomanNumeral:
        // No cast is required:
        roman = binary;

        System.Console.WriteLine((int)binary);
        System.Console.WriteLine(binary);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    10
    Conversion not yet implemented
*/

Robust Programming

  • In the previous example, the statement:

    C#
    binary = (BinaryNumeral)(int)roman;
    

    performs a conversion from a RomanNumeral to a BinaryNumeral. Because there is no direct conversion from RomanNumeral to BinaryNumeral, a cast is used to convert from a RomanNumeral to an int, and another cast to convert from an int to a BinaryNumeral.

  • Also the statement

    C#
    roman = binary;
    

    performs a conversion from a BinaryNumeral to a RomanNumeral. Because RomanNumeral defines an implicit conversion from BinaryNumeral, no cast is required.






How to: Use Operator Overloading to Create a Complex Number Class


This example shows how you can use operator overloading to create a complex number class Complex that defines complex addition. The program displays the imaginary and the real parts of the numbers and the addition result using an override of the ToString method.

Example

C#
public struct Complex
{
    public int real;
    public int imaginary;

    // Constructor.
    public Complex(int real, int imaginary)  
    {
        this.real = real;
        this.imaginary = imaginary;
    }

    // Specify which operator to overload (+), 
    // the types that can be added (two Complex objects),
    // and the return type (Complex).
    public static Complex operator +(Complex c1, Complex c2)
    {
        return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);
    }

    // Override the ToString() method to display a complex number 
    // in the traditional format:
    public override string ToString()
    {
        return (System.String.Format("{0} + {1}i", real, imaginary));
    }
}

class TestComplex
{
    static void Main()
    {
        Complex num1 = new Complex(2, 3);
        Complex num2 = new Complex(3, 4);

        // Add two Complex objects by using the overloaded + operator.
        Complex sum = num1 + num2;

        // Print the numbers and the sum by using the overridden 
        // ToString method.
        System.Console.WriteLine("First complex number:  {0}", num1);
        System.Console.WriteLine("Second complex number: {0}", num2);
        System.Console.WriteLine("The sum of the two numbers: {0}", sum);

        // Keep the console window open in debug mode.
        System.Console.WriteLine("Press any key to exit.");
        System.Console.ReadKey();
    }
}
/* Output:
    First complex number:  2 + 3i
    Second complex number: 3 + 4i
    The sum of the two numbers: 5 + 7i
*/






Equality Comparisons


It is sometimes necessary to compare two values for equality. In some cases, you are testing for value equality, also known as equivalence, which means that the values that are contained by the two variables are equal. In other cases, you have to determine whether two variables refer to the same underlying object in memory. This type of equality is called reference equality, or identity. This topic describes these two kinds of equality and provides links to other topics for more information.

Reference Equality

Reference equality means that two object references refer to the same underlying object. This can occur through simple assignment, as shown in the following example.

C#
using System;
class Test
{
    public int Num { get; set; }
    public string Str { get; set; }

    static void Main()
    {
        Test a = new Test() { Num = 1, Str = "Hi" };
        Test b = new Test() { Num = 1, Str = "Hi" };

        bool areEqual = System.Object.ReferenceEquals(a, b);
        // False:
        System.Console.WriteLine("ReferenceEquals(a, b) = {0}", areEqual);

        // Assign b to a.
        b = a;

        // Repeat calls with different results.
        areEqual = System.Object.ReferenceEquals(a, b);
        // True:
        System.Console.WriteLine("ReferenceEquals(a, b) = {0}", areEqual);

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}

In this code, two objects are created, but after the assignment statement, both references refer to the same object. Therefore they have reference equality. Use the ReferenceEquals method to determine whether two references refer to the same object.

The concept of reference equality applies only to reference types. Value type objects cannot have reference equality because when an instance of a value type is assigned to a variable, a copy of the value is made. Therefore you can never have two unboxed structs that refer to the same location in memory. Furthermore, if you use ReferenceEquals to compare two value types, the result will always be false, even if the values that are contained in the objects are all identical. This is because each variable is boxed into a separate object instance. For more information, see How to: Test for Reference Equality (Identity).

Value Equality

Value equality means that two objects contain the same value or values. For primitive value types such as int or bool, tests for value equality are straightforward. You can use the == operator, as shown in the following example.

C#
int a = GetOriginalValue();  
int b = GetCurrentValue();  

// Test for value equality.   
if( b == a)   
{  
    // The two integers are equal.  
}  

For most other types, testing for value equality is more complex because it requires that you understand how the type defines it. For classes and structs that have multiple fields or properties, value equality is often defined to mean that all fields or properties have the same value. For example, two Point objects might be defined to be equivalent if pointA.X is equal to pointB.X and pointA.Y is equal to pointB.Y.

However, there is no requirement that equivalence be based on all the fields in a type. It can be based on a subset. When you compare types that you do not own, you should make sure to understand specifically how equivalence is defined for that type. For more information about how to define value equality in your own classes and structs, see How to: Define Value Equality for a Type.

Value Equality for Floating Point Values

Equality comparisons of floating point values (double and float) are problematic because of the imprecision of floating point arithmetic on binary computers. For more information, see the remarks in the topic System.Double.

TitleDescription
How to: Test for Reference Equality (Identity)Describes how to determine whether two variables have reference equality.
How to: Define Value Equality for a TypeDescribes how to provide a custom definition of value equality for a type.
C# Programming GuideProvides links to detailed information about important C# language features and features that are available to C# through the .NET Framework.
TypesProvides information about the C# type system and links to additional information.






How to: Define Value Equality for a Type


When you define a class or struct, you decide whether it makes sense to create a custom definition of value equality (or equivalence) for the type. Typically, you implement value equality when objects of the type are expected to be added to a collection of some sort, or when their primary purpose is to store a set of fields or properties. You can base your definition of value equality on a comparison of all the fields and properties in the type, or you can base the definition on a subset. But in either case, and in both classes and structs, your implementation should follow the five guarantees of equivalence:

  1. x.Equals(x) returns true. This is called the reflexive property.

  2. x.Equals(y) returns the same value as y.Equals(x). This is called the symmetric property.

  3. if (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true. This is called the transitive property.

  4. Successive invocations of x.Equals(y) return the same value as long as the objects referenced by x and y are not modified.

  5. x.Equals(null) returns false. However, null.Equals(null) throws an exception; it does not obey rule number two above.

Any struct that you define already has a default implementation of value equality that it inherits from the System.ValueType override of the Object.Equals(Object) method. This implementation uses reflection to examine all the fields and properties in the type. Although this implementation produces correct results, it is relatively slow compared to a custom implementation that you write specifically for the type.

The implementation details for value equality are different for classes and structs. However, both classes and structs require the same basic steps for implementing equality:

  1. Override the virtual Object.Equals(Object) method. In most cases, your implementation of bool Equals( object obj ) should just call into the type-specific Equals method that is the implementation of the System.IEquatable<T> interface. (See step 2.)

  2. Implement the System.IEquatable<T> interface by providing a type-specific Equals method. This is where the actual equivalence comparison is performed. For example, you might decide to define equality by comparing only one or two fields in your type. Do not throw exceptions from Equals. For classes only: This method should examine only fields that are declared in the class. It should call base.Equals to examine fields that are in the base class. (Do not do this if the type inherits directly from Object, because the Object implementation of Object.Equals(Object) performs a reference equality check.)

  3. Optional but recommended: Overload the == and != operators.

  4. Override Object.GetHashCode so that two objects that have value equality produce the same hash code.

  5. Optional: To support definitions for "greater than" or "less than," implement the IComparable<T>interface for your type, and also overload the <= and >= operators.

The first example that follows shows a class implementation. The second example shows a struct implementation.

Example

The following example shows how to implement value equality in a class (reference type).

C#


namespace ValueEquality
{
    using System;
    class TwoDPoint : IEquatable<TwoDPoint>
    {
        // Readonly auto-implemented properties.
        public int X { get; private set; }
        public int Y { get; private set; }

        // Set the properties in the constructor.
        public TwoDPoint(int x, int y)
        {
            if ((x < 1) || (x > 2000) || (y < 1) || (y > 2000))
            {
                throw new System.ArgumentException("Point must be in range 1 - 2000");
            }
            this.X = x;
            this.Y = y;
        }

        public override bool Equals(object obj)
        {
            return this.Equals(obj as TwoDPoint);
        }

        public bool Equals(TwoDPoint p)
        {
            // If parameter is null, return false.
            if (Object.ReferenceEquals(p, null))
            {
                return false;
            }

            // Optimization for a common success case.
            if (Object.ReferenceEquals(this, p))
            {
                return true;
            }

            // If run-time types are not exactly the same, return false.
            if (this.GetType() != p.GetType())
            {
                return false;
            }

            // Return true if the fields match.
            // Note that the base class is not invoked because it is
            // System.Object, which defines Equals as reference equality.
            return (X == p.X) && (Y == p.Y);
        }

        public override int GetHashCode()
        {
            return X * 0x00010000 + Y;
        }

        public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)
        {
            // Check for null on left side.
            if (Object.ReferenceEquals(lhs, null))
            {
                if (Object.ReferenceEquals(rhs, null))
                {
                    // null == null = true.
                    return true;
                }

                // Only the left side is null.
                return false;
            }
            // Equals handles case of null on right side.
            return lhs.Equals(rhs);
        }

        public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs)
        {
            return !(lhs == rhs);
        }
    }

    // For the sake of simplicity, assume a ThreeDPoint IS a TwoDPoint.
    class ThreeDPoint : TwoDPoint, IEquatable<ThreeDPoint>
    {
        public int Z { get; private set; }

        public ThreeDPoint(int x, int y, int z)
            : base(x, y)
        {
            if ((z < 1) || (z > 2000))
            {
                throw new System.ArgumentException("Point must be in range 1 - 2000");
            }    
            this.Z = z;
        }

        public override bool Equals(object obj)
        {
            return this.Equals(obj as ThreeDPoint);
        }

        public bool Equals(ThreeDPoint p)
        {
            // If parameter is null, return false.
            if (Object.ReferenceEquals(p, null))
            {
                return false;
            }

            // Optimization for a common success case.
            if (Object.ReferenceEquals(this, p))
            {
                return true;
            }

            // Check properties that this class declares.
            if (Z == p.Z)
            {
                // Let base class check its own fields 
                // and do the run-time type comparison.
                return base.Equals((TwoDPoint)p);
            }
            else
            {
                return false;
            }    
        }

        public override int GetHashCode()
        {
            return (X * 0x100000) + (Y * 0x1000) + Z;
        }

        public static bool operator ==(ThreeDPoint lhs, ThreeDPoint rhs)
        {
            // Check for null.
            if (Object.ReferenceEquals(lhs, null))
            {
                if (Object.ReferenceEquals(rhs, null))
                {
                    // null == null = true.
                    return true;
                }

                // Only the left side is null.
                return false;
            }
            // Equals handles the case of null on right side.
            return lhs.Equals(rhs);
        }

        public static bool operator !=(ThreeDPoint lhs, ThreeDPoint rhs)
        {
            return !(lhs == rhs);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ThreeDPoint pointA = new ThreeDPoint(3, 4, 5);
            ThreeDPoint pointB = new ThreeDPoint(3, 4, 5);
            ThreeDPoint pointC = null;
            int i = 5;

            Console.WriteLine("pointA.Equals(pointB) = {0}", pointA.Equals(pointB));
            Console.WriteLine("pointA == pointB = {0}", pointA == pointB);
            Console.WriteLine("null comparison = {0}", pointA.Equals(pointC));
            Console.WriteLine("Compare to some other type = {0}", pointA.Equals(i));

            TwoDPoint pointD = null;
            TwoDPoint pointE = null;



            Console.WriteLine("Two null TwoDPoints are equal: {0}", pointD == pointE);

            pointE = new TwoDPoint(3, 4);
            Console.WriteLine("(pointE == pointA) = {0}", pointE == pointA);
            Console.WriteLine("(pointA == pointE) = {0}", pointA == pointE);
            Console.WriteLine("(pointA != pointE) = {0}", pointA != pointE);

            System.Collections.ArrayList list = new System.Collections.ArrayList();
            list.Add(new ThreeDPoint(3, 4, 5));
            Console.WriteLine("pointE.Equals(list[0]): {0}", pointE.Equals(list[0]));

            // Keep the console window open in debug mode.
            System.Console.WriteLine("Press any key to exit.");
            System.Console.ReadKey();
        }
    }

    /* Output:
        pointA.Equals(pointB) = True
        pointA == pointB = True
        null comparison = False
        Compare to some other type = False
        Two null TwoDPoints are equal: True
        (pointE == pointA) = False
        (pointA == pointE) = False
        (pointA != pointE) = True
        pointE.Equals(list[0]): False
    */
}

On classes (reference types), the default implementation of both Object.Equals(Object) methods performs a reference equality comparison, not a value equality check. When an implementer overrides the virtual method, the purpose is to give it value equality semantics.

The == and != operators can be used with classes even if the class does not overload them. However, the default behavior is to perform a reference equality check. In a class, if you overload the Equals method, you should overload the == and != operators, but it is not required.

Example

The following example shows how to implement value equality in a struct (value type):

C#
    struct TwoDPoint : IEquatable<TwoDPoint>
    {
        // Read/write auto-implemented properties.
        public int X { get; private set; }
        public int Y { get; private set; }

        public TwoDPoint(int x, int y)
            : this()
        {
            X = x;
            Y = x;
        }

        public override bool Equals(object obj)
        {
            if (obj is TwoDPoint)
            {
                return this.Equals((TwoDPoint)obj);
            }
            return false;
        }

        public bool Equals(TwoDPoint p)
        {
            return (X == p.X) && (Y == p.Y);
        }

        public override int GetHashCode()
        {
            return X ^ Y;
        }

        public static bool operator ==(TwoDPoint lhs, TwoDPoint rhs)
        {
            return lhs.Equals(rhs);
        }

        public static bool operator !=(TwoDPoint lhs, TwoDPoint rhs)
        {
            return !(lhs.Equals(rhs));
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            TwoDPoint pointA = new TwoDPoint(3, 4);
            TwoDPoint pointB = new TwoDPoint(3, 4);
            int i = 5;

            // Compare using virtual Equals, static Equals, and == and != operators.
            // True:
            Console.WriteLine("pointA.Equals(pointB) = {0}", pointA.Equals(pointB));
            // True:
            Console.WriteLine("pointA == pointB = {0}", pointA == pointB);
            // True:
            Console.WriteLine("Object.Equals(pointA, pointB) = {0}", Object.Equals(pointA, pointB));
            // False:
            Console.WriteLine("pointA.Equals(null) = {0}", pointA.Equals(null));
            // False:
            Console.WriteLine("(pointA == null) = {0}", pointA == null);
            // True:
            Console.WriteLine("(pointA != null) = {0}", pointA != null);
            // False:
            Console.WriteLine("pointA.Equals(i) = {0}", pointA.Equals(i));
            // CS0019:
            // Console.WriteLine("pointA == i = {0}", pointA == i); 

            // Compare unboxed to boxed.
            System.Collections.ArrayList list = new System.Collections.ArrayList();
            list.Add(new TwoDPoint(3, 4));
            // True:
            Console.WriteLine("pointE.Equals(list[0]): {0}", pointA.Equals(list[0]));


            // Compare nullable to nullable and to non-nullable.
            TwoDPoint? pointC = null;
            TwoDPoint? pointD = null;
            // False:
            Console.WriteLine("pointA == (pointC = null) = {0}", pointA == pointC);
            // True:
            Console.WriteLine("pointC == pointD = {0}", pointC == pointD);

            TwoDPoint temp = new TwoDPoint(3, 4);
            pointC = temp;
            // True:
            Console.WriteLine("pointA == (pointC = 3,4) = {0}", pointA == pointC);

            pointD = temp;
            // True:
            Console.WriteLine("pointD == (pointC = 3,4) = {0}", pointD == pointC);

            // Keep the console window open in debug mode.
            System.Console.WriteLine("Press any key to exit.");
            System.Console.ReadKey();
        }
    }

    /* Output:
        pointA.Equals(pointB) = True
        pointA == pointB = True
        Object.Equals(pointA, pointB) = True
        pointA.Equals(null) = False
        (pointA == null) = False
        (pointA != null) = True
        pointA.Equals(i) = False
        pointE.Equals(list[0]): True
        pointA == (pointC = null) = False
        pointC == pointD = True
        pointA == (pointC = 3,4) = True
        pointD == (pointC = 3,4) = True
    */
}

For structs, the default implementation of Object.Equals(Object) (which is the overridden version in System.ValueType) performs a value equality check by using reflection to compare the values of every field in the type. When an implementer overrides the virtual Equals method in a struct, the purpose is to provide a more efficient means of performing the value equality check and optionally to base the comparison on some subset of the struct's field or properties.

The == and != operators cannot operate on a struct unless the struct explicitly overloads them.






How to: Test for Reference Equality


You do not have to implement any custom logic to support reference equality comparisons in your types. This functionality is provided for all types by the static Object.ReferenceEquals method.

The following example shows how to determine whether two variables have reference equality, which means that they refer to the same object in memory.

The example also shows why Object.ReferenceEquals always returns false for value types and why you should not use ReferenceEquals to determine string equality.

Example

C#
namespace TestReferenceEquality
{
    struct TestStruct
    {
        public int Num { get; private set; }
        public string Name { get; private set; }

        public TestStruct(int i, string s) : this()
        {
            Num = i;
            Name = s;
        }
    }

    class TestClass
    {
        public int Num { get; set; }
        public string Name { get; set; }
    }

    class Program
    {
        static void Main()
        {
            // Demonstrate reference equality with reference types.
            #region ReferenceTypes

            // Create two reference type instances that have identical values.
            TestClass tcA = new TestClass() { Num = 1, Name = "New TestClass" };
            TestClass tcB = new TestClass() { Num = 1, Name = "New TestClass" };

            Console.WriteLine("ReferenceEquals(tcA, tcB) = {0}",
                                Object.ReferenceEquals(tcA, tcB)); // false

            // After assignment, tcB and tcA refer to the same object. 
            // They now have reference equality. 
            tcB = tcA;
            Console.WriteLine("After asignment: ReferenceEquals(tcA, tcB) = {0}",
                                Object.ReferenceEquals(tcA, tcB)); // true

            // Changes made to tcA are reflected in tcB. Therefore, objects
            // that have reference equality also have value equality.
            tcA.Num = 42;
            tcA.Name = "TestClass 42";
            Console.WriteLine("tcB.Name = {0} tcB.Num: {1}", tcB.Name, tcB.Num);
            #endregion

            // Demonstrate that two value type instances never have reference equality.
            #region ValueTypes

            TestStruct tsC = new TestStruct( 1, "TestStruct 1");

            // Value types are copied on assignment. tsD and tsC have 
            // the same values but are not the same object.
            TestStruct tsD = tsC;
            Console.WriteLine("After asignment: ReferenceEquals(tsC, tsD) = {0}",
                                Object.ReferenceEquals(tsC, tsD)); // false
            #endregion

            #region stringRefEquality
            // Constant strings within the same assembly are always interned by the runtime.
            // This means they are stored in the same location in memory. Therefore, 
            // the two strings have reference equality although no assignment takes place.
            string strA = "Hello world!";
            string strB = "Hello world!";
            Console.WriteLine("ReferenceEquals(strA, strB) = {0}",
                             Object.ReferenceEquals(strA, strB)); // true

            // After a new string is assigned to strA, strA and strB
            // are no longer interned and no longer have reference equality.
            strA = "Goodbye world!";
            Console.WriteLine("strA = \"{0}\" strB = \"{1}\"", strA, strB);
            
            Console.WriteLine("After strA changes, ReferenceEquals(strA, strB) = {0}",
                            Object.ReferenceEquals(strA, strB)); // false
            
            // A string that is created at runtime cannot be interned.
            StringBuilder sb = new StringBuilder("Hello world!");
            string stringC = sb.ToString(); 
            // False:
            Console.WriteLine("ReferenceEquals(stringC, strB) = {0}",
                            Object.ReferenceEquals(stringC, strB));

            // The string class overloads the == operator to perform an equality comparison.
            Console.WriteLine("stringC == strB = {0}", stringC == strB); // true

            #endregion

            // Keep the console open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

/* Output:
    ReferenceEquals(tcA, tcB) = False
    After asignment: ReferenceEquals(tcA, tcB) = True
    tcB.Name = TestClass 42 tcB.Num: 42
    After asignment: ReferenceEquals(tsC, tsD) = False
    ReferenceEquals(strA, strB) = True
    strA = "Goodbye world!" strB = "Hello world!"
    After strA changes, ReferenceEquals(strA, strB) = False
*/

The implementation of Equals in the System.Object universal base class also performs a reference equality check, but it is best not to use this because, if a class happens to override the method, the results might not be what you expect. The same is true for the == and != operators. When they are operating on reference types, the default behavior of == and != is to perform a reference equality check. However, derived classes can overload the operator to perform a value equality check. To minimize the potential for error, it is best to always use ReferenceEquals when you have to determine whether two objects have reference equality.+

Constant strings within the same assembly are always interned by the runtime. That is, only one instance of each unique literal string is maintained. However, the runtime does not guarantee that strings created at runtime are interned, nor does it guarantee that two equal constant strings in different assemblies are interned.



'프로그래밍 > C#' 카테고리의 다른 글

C# Programming Guide - Types  (0) 2017.05.10
C# Programming Guide - Inside a C# Program  (0) 2017.05.10
C# Programming Guide - Arrays  (0) 2017.05.09
Tuple Class  (0) 2017.05.08
Memory Management and Garbage Collection in the .NET Framework  (0) 2017.05.08
:
Posted by 지훈2