C# Programming Guide - Statements, Expressions, and Operators 프로그래밍/C#2017. 5. 10. 15:35
- Statements, Expressions, and Operators
- Statements
- Expressions
- Operators
- Anonymous Functions
- Anonymous Functions - Lambda Expressions
- How to: Use Lambda Expressions in a Query
- How to: Use Lambda Expressions Outside LINQ
- Anonymous Methods
- Overloadable Operators
- Conversion Operators
- Using Conversion Operators
- How to: Implement User-Defined Conversions Between Structs
- How to: Use Operator Overloading to Create a Complex Number Class
- Equality Comparisons
- How to: Define Value Equality for a Type
- How to: Test for Reference Equality
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:
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:
Category | C# keywords / notes |
---|---|
Declaration statements | A 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#
|
Expression statements | Expression statements that calculate a value must store the value in a variable. C#
|
Selection statements enable you to branch to different sections of code, depending on one or more specified conditions. For more information, see the following topics: , , , | |
Iteration 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: , , , , | |
Jump statements transfer control to another section of code. For more information, see the following topics: , , , , , | |
Exception handling statements enable you to gracefully recover from exceptional conditions that occur at run time. For more information, see the following topics: , , , | |
Checked 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 | and .|
he await statement | If you mark a method with the 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 . For more information, see . | modifier, you can use the operator in the method. When control reaches an
The yield return statement | An iterator performs a custom iteration over a collection, such as a list or an array. An iterator uses the 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 . | statement to return each element one at a time. When a
The fixed statement | The fixed statement prevents the garbage collector from relocating a movable variable. For more information, see | .
The lock statement | The lock statement enables you to limit access to blocks of code to only one thread at a time. For more information, see | .
Labeled statements | You can give a statement a label and then use the | keyword to jump to the labeled statement. (See the example in the following row.)
The empty statement | The 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#
|
Embedded Statements
Some statements, including
, , , and , 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:// 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:
if(pointB == true)
//Error CS1023:
int radius = 5;
Put the embedded statement in a block to fix the error:
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:
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:
// 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:
((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 objects), method group names (as distinct from specific methods), and event and 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 .
Overflows
Numeric expressions may cause overflows if the value is larger than the maximum value of the value's type. For more information, see
and .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
.Most expressions, except assignment expressions and method invocation expressions, must be embedded in a statement. For more information, see
.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:
// Expression statements.
int i = 5;
string s = "Hello World";
For more information on literals, see
.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:
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.
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
. A delegate invocation uses the name of a delegate and method parameters in parenthesis. For more information, see . 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
.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
.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
.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
.
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
.
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 precedence, associativity, 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
Expression | Description |
---|---|
x x?.y | yMember access Conditional member access |
f | Method and delegate invocation |
a a?[x] | Array and indexer access Conditional array and indexer access |
x | Post-increment |
x | Post-decrement |
T(...) | Object and delegate creation |
new T(...){...} | Object creation with initializer. See | .
new {...} | Anonymous object initializer. See | .
new T[...] | Array creation. See | .
(T) | Obtain System.Type object for T |
(x) | Evaluate expression in checked context |
(x) | Evaluate expression in unchecked context |
(T) | Obtain default value of type T |
{} | Anonymous function (anonymous method) |
Unary Operators
Expression | Description |
---|---|
x | Identity |
x | Negation |
x | Logical negation |
x | Bitwise negation |
x | Pre-increment |
x | Pre-decrement |
x | Explicitly convert x to type T |
Multiplicative Operators
Expression | Description |
---|---|
Multiplication | |
Division | |
Remainder |
Additive Operators
Expression | Description |
---|---|
x | yAddition, string concatenation, delegate combination |
x | ySubtraction, delegate removal |
Shift Operators
Expression | Description |
---|---|
x | yShift left |
x | yShift right |
Relational and Type Operators
Expression | Description |
---|---|
x | yLess than |
x | yGreater than |
x | yLess than or equal |
x | yGreater than or equal |
x | TReturn true if x is a T, false otherwise |
x | TReturn x typed as T, or null if x is not a T |
Equality Operators
Expression | Description |
---|---|
x | yEqual |
x | yNot equal |
Logical, Conditional, and Null Operators
Category | Expression | Description |
---|---|---|
Logical AND | x | yInteger bitwise AND, Boolean logical AND |
Logical XOR | x | yInteger bitwise XOR, boolean logical XOR |
Logical OR | x | yInteger bitwise OR, boolean logical OR |
Conditional AND | x | yEvaluates y only if x is true |
Conditional OR | x | yEvaluates y only if x is false |
Null coalescing | x | yEvaluates to y if x is null, to x otherwise |
Conditional | x | y : zEvaluates to y if x is true, z if x is false |
Assignment and Anonymous Operators
Expression | Description |
---|---|
Assignment | |
x op= y | Compound 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.
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.
Statement | Order of evaluation |
---|---|
a = b | a, b, = |
a = b + c | a, b, c, +, = |
a = b + c * d | a, b, c, d, *, +, = |
a = b * c + d | a, b, c, *, d, +, = |
a = b - c + d | a, b, c, -, d, +, = |
a += b -= c | a, 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.
Statement | Order of evaluation |
---|---|
a = (b + c) * d | a, 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
.Related Sections
For more information, see
and .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:
.
Note
Lambda expressions can be bound to expression trees and also to delegates.
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:
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 that you can use to create or 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 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:
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:
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 (see “Associativity” section of the Operators article).
Lambdas are used in method-based LINQ queries as arguments to standard query operator methods such as
.When you use method-based syntax to call 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
or operator.All restrictions that apply to anonymous methods also apply to lambda expressions. For more information, see
.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
. An expression lambda returns the result of the expression and takes the following basic form:(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:
(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:
(int x, string s) => s.Length > x
Specify zero input parameters with empty parentheses:
() => 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.
delegate void TestDelegate(string s);
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 ExampleMethodAsync
.
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.
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
.Lambdas with the Standard Query Operators
Many Standard query operators have an input parameter whose type is one of the 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:
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:
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
method, is shown here: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:
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 (>=
).
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:
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
type to which the lambda expression is converted.Variable Scope in Lambda Expressions
Lambdas can refer to outer variables (see
) 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: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
orout
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, orcontinue
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 .
Example
The following example demonstrates how to use a lambda expression in a method-based query by using the Expression<Func\<int,bool>>
but the lambda expression would look exactly the same. For more information on the Expression type, see .
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
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.
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 ( and ) are inferred by the compiler and do not have to be explicitly given in the lambda input parameters.
Example
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 was to use . 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 .
Creating anonymous methods is essentially a way to pass a code block as a delegate parameter. Here are two examples:
// Create a handler for a click event.
button1.Click += delegate(System.Object o, System.EventArgs e)
{ System.Windows.Forms.MessageBox.Show("Click!"); };
// 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.
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 goto
, break
, 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:
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
or 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
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.
// 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 keyword. Not all operators can be overloaded, however, and others have restrictions, as listed in this table:
Operators | Overloadability |
---|---|
, , , , , , , | These 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 | , which can be overloaded. |
The array indexing operator cannot be overloaded, but you can define indexers. | |
The cast operator cannot be overloaded, but you can define new conversion operators (see | and ).|
, , , , , , , , , | Assignment operators cannot be overloaded, but += , for example, is evaluated using + , which can be overloaded. |
, , , , , , , , , , , , , , , | These 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.
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.
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
.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.
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
.
Related Sections
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.
Note
For information about simple type conversions, see
, , , or .Example
This is an example of an explicit conversion operator. This operator converts from the type 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.
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 type. Because any digit can be converted to a , there's no need to force users to be explicit about the conversion.
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
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 aBinaryNumeral
. Because there is no direct conversion fromRomanNumeral
toBinaryNumeral
, a cast is used to convert from aRomanNumeral
to anint
, and another cast to convert from anint
to aBinaryNumeral
.Also the statement
C#roman = binary;
performs a conversion from a
BinaryNumeral
to aRomanNumeral
. BecauseRomanNumeral
defines an implicit conversion fromBinaryNumeral
, 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
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.
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
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 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 .
Value Equality
Value equality means that two objects contain the same value or values. For primitive value types such as
or , tests for value equality are straightforward. You can use the operator, as shown in the following example.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
.Value Equality for Floating Point Values
Equality comparisons of floating point values (
and ) are problematic because of the imprecision of floating point arithmetic on binary computers. For more information, see the remarks in the topic .Related Topics
Title | Description |
---|---|
Describes how to determine whether two variables have reference equality. | |
Describes how to provide a custom definition of value equality for a type. | |
Provides links to detailed information about important C# language features and features that are available to C# through the .NET Framework. | |
Provides 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:
x.
Equals
(x) returnstrue.
This is called the reflexive property.x.
Equals
(y) returns the same value as y.Equals
(x). This is called the symmetric property.if (x.
Equals
(y) && y.Equals
(z)) returnstrue
, then x.Equals
(z) returnstrue
. This is called the transitive property.Successive invocations of x.
Equals
(y) return the same value as long as the objects referenced by x and y are not modified.x.
Equals
(null) returnsfalse
. 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
override of the 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:
Override the
method. In most cases, your implementation ofbool Equals( object obj )
should just call into the type-specificEquals
method that is the implementation of the interface. (See step 2.)Implement the
interface by providing a type-specificEquals
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 fromEquals
. For classes only: This method should examine only fields that are declared in the class. It should callbase.Equals
to examine fields that are in the base class. (Do not do this if the type inherits directly from , because the implementation of performs a reference equality check.)Optional but recommended: Overload the
and operators.Override
so that two objects that have value equality produce the same hash code.Optional: To support definitions for "greater than" or "less than," implement the
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).
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
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):
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 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 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 false
for value types and why you should not use to determine string equality.
Example
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 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 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 |