Dotnet Csharp
Dotnet Csharp
C# documentation
Learn how to write any application using the C# programming language on the .NET
platform.
Learn to program
b GET STARTED
q VIDEO
g TUTORIAL
Self-guided tutorials
In-browser tutorial
i REFERENCE
C# on Q&A
C# on Stack Overflow
C# on Discord
Fundamentals
e OVERVIEW
A tour of C#
Inside a C# program
p CONCEPT
Type system
Object oriented programming
Functional techniques
Exceptions
Coding style
g TUTORIAL
Display command-line
Intro to classes
Object oriented C#
Converting types
Pattern matching
What's new
h WHAT'S NEW
What's new in C# 13
What's new in C# 12
What's new in C# 11
What's new in C# 10
g TUTORIAL
i REFERENCE
Version compatibility
Key concepts
e OVERVIEW
C# language strategy
Programming concepts
p CONCEPT
Asynchronous programming
Advanced concepts
i REFERENCE
Expression trees
Native interoperability
Performance engineering
Stay in touch
i REFERENCE
YouTube
Twitter
A tour of the C# language
Article • 05/08/2024
The C# language is the most popular language for the .NET platform, a free, cross-
platform, open source development environment. C# programs can run on many
different devices, from Internet of Things (IoT) devices to the cloud and everywhere in
between. You can write apps for phone, desktop, and laptop computers and servers.
Hello world
The "Hello, World" program is traditionally used to introduce a programming language.
Here it is in C#:
C#
The line starting with // is a single line comment. C# single line comments start with //
and continue to the end of the current line. C# also supports multi-line comments.
Multi-line comments start with /* and end with */ . The WriteLine method of the
Console class, which is in the System namespace, produces the output of the program.
This class is provided by the standard class libraries, which, by default, are automatically
referenced in every C# program.
The preceding example shows one form of a "Hello, World" program, using top-level
statements. Earlier versions of C# required you to define the program's entry point in a
method. This format is still valid, and you'll see it in many existing C# samples. You
should be familiar with this form as well, as shown in the following example:
C#
using System;
class Hello
{
static void Main()
{
// This line prints "Hello, World"
Console.WriteLine("Hello, World");
}
}
This version shows the building blocks you use in your programs. The "Hello, World"
program starts with a using directive that references the System namespace.
Namespaces provide a hierarchical means of organizing C# programs and libraries.
Namespaces contain types and other namespaces—for example, the System namespace
contains many types, such as the Console class referenced in the program, and many
other namespaces, such as IO and Collections . A using directive that references a
given namespace enables unqualified use of the types that are members of that
namespace. Because of the using directive, the program can use Console.WriteLine as
shorthand for System.Console.WriteLine . In the earlier example, that namespace was
implicitly included.
The Hello class declared by the "Hello, World" program has a single member, the
method named Main . The Main method is declared with the static modifier. While
instance methods can reference a particular enclosing object instance using the keyword
this , static methods operate without reference to a particular object. By convention,
when there are no top-level statements a static method named Main serves as the entry
point of a C# program.
Both entry point forms produce equivalent code. When you use top-level statements,
the compiler synthesizes the containing class and method for the program's entry point.
Tip
The examples in this article give you a first look at C# code. Some samples may
show elements of C# that you're not familiar with. When you're ready to learn C#,
start with our beginner tutorials, or dive into the links in each section. If you're
experienced in Java, JavaScript, TypeScript or Python, read our tips to help you
find the information you need to quickly learn C#.
Familiar C# features
C# is approachable for beginners yet offers advanced features for experienced
developers writing specialized applications. You can be productive quickly. You can learn
more specialized techniques as you need them for your applications.
C# apps benefit from the .NET Runtime's automatic memory management. C# apps also
use the extensive runtime libraries provided by the .NET SDK. Some components are
platform independent, like file system libraries, data collections, and math libraries.
Others are specific to a single workload, like the ASP.NET Core web libraries, or the .NET
MAUI UI library. A rich Open Source ecosystem on NuGet augments the libraries that
are part of the runtime. These libraries provide even more components you can use.
C# is a strongly typed language. Every variable you declare has a type known at compile
time. The compiler, or editing tools tell you if you're using that type incorrectly. You can
fix those errors before you ever run your program. Fundamental data types are built into
the language and runtime: value types like int , double , char , reference types like
string , arrays, and other collections. As you write your programs, you create your own
types. Those types can be struct types for values, or class types that define object-
oriented behavior. You can add the record modifier to either struct or class types so
the compiler synthesizes code for equality comparisons. You can also create interface
definitions, which define a contract, or a set of members, that a type implementing that
interface must provide. You can also define generic types and methods. Generics use
type parameters to provide a placeholder for an actual type when used.
As you write code, you define functions, also called methods, as members of struct and
class types. These methods define the behavior of your types. Methods can be
C# apps use exceptions to report and handle errors. You'll be familiar with this practice if
you've used C++ or Java. Your code throws an exception when it can't do what was
intended. Other code, no matter how many levels up the call stack, can optionally
recover by using a try - catch block.
Distinctive C# features
Some elements of C# might be less familiar. Language integrated query (LINQ) provides
a common pattern-based syntax to query or transform any collection of data. LINQ
unifies the syntax for querying in-memory collections, structured data like XML or JSON,
database storage, and even cloud based data APIs. You learn one set of syntax and you
can search and manipulate data regardless of its storage. The following query finds all
students whose grade point average is greater than 3.5:
C#
The preceding query works for many storage types represented by Students . It could be
a collection of objects, a database table, a cloud storage blob, or an XML structure. The
same query syntax works for all storage types.
The Task based asynchronous programming model enables you to write code that reads
as though it runs synchronously, even though it runs asynchronously. It utilizes the
async and await keywords to describe methods that are asynchronous, and when an
C#
C#
C#
C# provides pattern matching. Those expressions enable you to inspect data and make
decisions based on its characteristics. Pattern matching provides a great syntax for
control flow based on data. The following code shows how methods for the boolean
and, or, and xor operations could be expressed using pattern matching syntax:
C#
Pattern matching expressions can be simplified using _ as a catch all for any value. The
following example shows how you can simplify the and method:
C#
Finally, as part of the .NET ecosystem, you can use Visual Studio , or Visual Studio
Code with the C# DevKit . These tools provide rich understanding of C#, including
the code you write. They also provide debugging capabilities.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Roadmap for Java developers learning
C#
Article • 04/09/2024
C# and Java have many similarities. As you learn C#, you can apply much of the
knowledge you already have from programming in Java:
1. Similar syntax: Both Java and C# are in the C family of languages. That similarity
means you can already read and understand C#. There are some differences, but
most of the syntax is the same as Java, and C. The curly braces and semicolons are
familiar. The control statements like if , else , switch are the same. The looping
statements of for , while , and do ... while are same. The same keywords for class
and interface are in both languages. The access modifiers from public to
private are the same. Even many of the builtin types use the same keywords: int ,
There are other features in C# that aren't in Java. You'll see features like async and await,
and using statements to automatically free nonmemory resources.
There are also some similar features between C# and Java that have subtle but
important differences:
1. Properties and Indexers: Both properties and indexers (treating a class like an array
or dictionary) have language support. In Java, they're naming conventions for
methods starting with get and set .
2. Records: In C#, records can be either class (reference) or struct (value) types. C#
records can be immutable, but aren't required to be immutable.
3. Tuples have different syntax in C# and Java.
4. Attributes are similar to Java annotations.
Finally, there are Java language features that aren't available in C#:
1. Checked exceptions: In C#, any method could theoretically throw any exception.
2. Checked array covariance: In C#, arrays aren't safely covariant. You should use the
generic collection classes and interfaces if you need covariant structures.
Overall, learning C# for a developer experienced in Java should be smooth. You'll find
enough familiar idioms to quickly be productive, and you'll learn the new idioms quickly.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
be found on GitHub, where you Select a link to provide feedback:
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Roadmap for JavaScript and TypeScript
developers learning C#
Article • 04/09/2024
C#, TypeScript and JavaScript are all members of the C family of languages. The
similarities between the languages help you quickly become productive in C#.
1. Similar syntax: JavaScript, TypeScript, and C# are in the C family of languages. That
similarity means you can already read and understand C#. There are some
differences, but most of the syntax is the same as JavaScript, and C. The curly
braces and semicolons are familiar. The control statements like if , else , switch
are the same. The looping statements of for , while , and do ... while are same. The
same keywords for class and interface are in both C# and TypeScript. The access
modifiers in TypeScript and C#, from public to private , are the same.
2. The => token: All languages support lightweight function definitions. In C#, they're
referred to as lambda expressions, in JavaScript, they're typically called arrow
functions.
3. Function hierarchies: All three languages support local functions, which are
functions defined in other functions.
4. Async / Await: All three languages share the same async and await keywords for
asynchronous programming.
5. Garbage collection: All three languages rely on a garbage collector for automatic
memory management.
6. Event model: C#'s event syntax is similar to JavaScript's model for document object
model (DOM) events.
7. Package manager: NuGet is the most common package manager for C# and
.NET, similar to npm for JavaScript applications. C# libraries are delivered in
assemblies.
As you continue learning C#, you'll learn concepts that aren't part of JavaScript. Some of
these concepts might be familiar to you if you use TypeScript:
1. C# Type System: C# is a strongly typed language. Every variable has a type, and
that type can't change. You define class or struct types. You can define interface
definitions that define behavior implemented by other types. TypeScript includes
many of these concepts, but because TypeScript is built on JavaScript, the type
system isn't as strict.
2. Pattern matching: Pattern matching enables concise conditional statements and
expressions based on the shape of complex data structures. The is expression
checks if a variable "is" some pattern. The pattern-based switch expression
provides a rich syntax to inspect a variable and make decisions based on its
characteristics.
3. String interpolation and raw string literals: String interpolation enables you to
insert evaluated expressions in a string, rather than using positional identifiers. Raw
string literals provide a way to minimize escape sequences in text.
4. Nullable and non-nullable types: C# supports nullable value types, and nullable
reference types by appending the ? suffix to a type. For nullable types, the
compiler warns you if you don't check for null before dereferencing the
expression. For non-nullable types, the compiler warns you if you might be
assigning a null value to that variable. These features can minimize your
application throwing a System.NullReferenceException. The syntax might be
familiar from TypeScript's use of ? for optional properties.
5. LINQ: Language integrated query (LINQ) provides a common syntax to query and
transform data, regardless of its storage.
As you learn more other differences become apparent, but many of those differences
are smaller in scope.
Some familiar features and idioms from JavaScript and TypeScript aren't available in C#:
1. dynamic types: C# uses static typing. A variable declaration includes the type, and
that type can't change. There is a dynamic type in C# that provides runtime
binding.
2. Prototypal inheritance: C# inheritance is part of the type declaration. A C# class
declaration states any base class. In JavaScript, you can set the __proto__ property
to set the base type on any instance.
3. Interpreted language: C# code must be compiled before you run it. JavaScript
code can be run directly in the browser.
1. Union types: C# doesn't support union types. However, design proposals are in
progress.
2. Decorators: C# doesn't have decorators. Some common decorators, such as
@sealed are reserved keywords in C#. Other common decorators might have
corresponding Attributes. For other decorators, you can create your own attributes.
3. More forgiving syntax: The C# compiler parses code more strictly than JavaScript
requires.
If you're building a web application, you should consider using Blazor to build your
application. Blazor is a full-stack web framework built for .NET and C#. Blazor
components can run on the server, as .NET assemblies, or on the client using
WebAssembly. Blazor supports interop with your favorite JavaScript or TypeScript
libraries.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Roadmap for Python developers
learning C#
Article • 04/09/2024
C# and Python share similar concepts. These familiar constructs help you learn C# when
you already know Python.
1. Object oriented: Both Python and C# are object-oriented languages. All the
concepts around classes in Python apply in C#, even if the syntax is different.
2. Cross-platform: Both Python and C# are cross-platform languages. Apps written in
either language can run on many platforms.
3. Garbage collection: Both languages employ automatic memory management
through garbage collection. The runtime reclaims the memory from objects that
aren't referenced.
4. Strongly typed: Both Python and C# are strongly typed languages. Type coercion
doesn't occur implicitly. There are differences described later, as C# is statically
typed whereas Python is dynamically typed.
5. Async / Await: Python's async and await feature was directly inspired by C#'s
async and await support.
As you start learning C#, you'll learn these important concepts where C# is different
than Python:
1. Indentation vs. tokens: In Python, newlines and indentation are first-class syntactic
elements. In C#, whitespace isn't significant. Tokens, like ; separate statements,
and other tokens { and } control block scope for if and other block statements.
However, for readability, most coding styles (including the style used in these docs)
use indentation to reinforce the block scopes declared by { and } .
2. Static typing: In C#, a variable declaration includes its type. Reassigning a variable
to an object of a different type generates a compiler error. In Python, the type can
change when reassigned.
3. Nullable types: C# variables can be nullable or non-nullable. A non-nullable type is
one that can't be null (or nothing). It always refers to a valid object. By contrast, a
nullable type might either refer to a valid object, or null.
4. LINQ: The query expression keywords that make up Language Integrated Query
(LINQ) aren't keywords in Python. However, Python libraries like itertools , more-
itertools , and py-linq provide similar functionality.
5. Generics: C# generics use C# static typing to make assertions about the arguments
supplied for type parameters. A generic algorithm might need to specify
constraints that an argument type must satisfy.
Finally, there are some features of Python that aren't available in C#:
1. Structural (duck) typing: In C#, types have names and declarations. Except for
tuples, types with the same structure aren't interchangeable.
2. REPL: C# doesn't have a Read-Eval-Print Loop (REPL) to quickly prototype
solutions.
3. Significant whitespace: You need to correctly use braces { and } to note block
scope.
Learning C# if you know Python is a smooth journey. The languages have similar
concepts and similar idioms to use.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
be found on GitHub, where you Select a link to provide feedback:
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Annotated C# strategy
Article • 02/21/2023
We will keep evolving C# to meet the changing needs of developers and remain a state-
of-the-art programming language. We will innovate eagerly and broadly in collaboration
with the teams responsible for .NET libraries, developer tools, and workload support,
while being careful to stay within the spirit of the language. Recognizing the diversity of
domains where C# is being used, we will prefer language and performance
improvements that benefit all or most developers and maintain a high commitment to
backwards compatibility. We will continue to empower the broader .NET ecosystem and
grow its role in C#’s future, while maintaining stewardship of design decisions.
The C# community continues to grow, and the C# language continues to evolve to meet
the community's needs and expectations. We draw inspiration from a variety of sources
to select features that benefit a large segment of C# developers, and that provide
consistent improvements in productivity, readability, and performance.
We evaluate new ideas in the spirit and history of the C# language. We prioritize
innovations that make sense to the majority of existing C# developers.
Developers use C# in all .NET workloads, such as web front and back ends, cloud native
development, desktop development and building cross platform applications. We focus
on new features that have the most impact either directly, or by empowering
improvements to common libraries. Language feature development includes integration
into our developer tools and learning resources.
"maintaining stewardship"
C# language design takes place in the open with community participation. Anyone
can propose new C# features in our GitHub repos . The Language Design Team
makes the final decisions after weighing community input.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
be found on GitHub, where you Select a link to provide feedback:
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Introduction to C#
Article • 03/15/2024
Welcome to the introduction to C# tutorials. These lessons start with interactive code
that you can run in your browser. You can learn the basics of C# from the C# for
Beginners video series before starting these interactive lessons.
https://www.youtube-nocookie.com/embed/9THmGiSPjBQ?si=3kUKFtOMLpEzeq7J
The first lessons explain C# concepts using small snippets of code. You'll learn the basics
of C# syntax and how to work with data types like strings, numbers, and booleans. It's all
interactive, and you'll be writing and running code within minutes. These first lessons
assume no prior knowledge of programming or the C# language.
You can try these tutorials in different environments. The concepts you'll learn are the
same. The difference is which experience you prefer:
In your browser, on the docs platform: This experience embeds a runnable C# code
window in docs pages. You write and execute C# code in the browser.
In the Microsoft Learn training experience. This learning path contains several
modules that teach the basics of C#.
In Jupyter on Binder . You can experiment with C# code in a Jupyter notebook on
binder.
On your local machine. After you've explored online, you can download the .NET
SDK and build programs on your machine.
All the introductory tutorials following the Hello World lesson are available using the
online browser experience or in your own local development environment. At the end of
each tutorial, you decide if you want to continue with the next lesson online or on your
own machine. There are links to help you set up your environment and continue with
the next tutorial on your machine.
Hello world
In the Hello world tutorial, you'll create the most basic C# program. You'll explore the
string type and how to work with text. You can also use the path on Microsoft Learn
Numbers in C#
In the Numbers in C# tutorial, you'll learn how computers store numbers and how to
perform calculations with different numeric types. You'll learn the basics of rounding,
and how to perform mathematical calculations using C#. This tutorial is also available to
run locally on your machine.
This tutorial assumes that you've finished the Hello world lesson.
This tutorial assumes that you've finished the Hello world and Numbers in C# lessons.
List collection
The List collection lesson gives you a tour of the List collection type that stores
sequences of data. You'll learn how to add and remove items, search for items, and sort
the lists. You'll explore different kinds of lists. This tutorial is also available to run locally
on your machine.
This tutorial assumes that you've finished the lessons listed above.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
Provide product feedback
more information, see our
contributor guide.
Set up your local environment
Article • 04/28/2022
We recommend Visual Studio for Windows. You can download a free version
from the Visual Studio downloads page . Visual Studio includes the .NET SDK.
You can also use the Visual Studio Code editor with the C# DevKit . You'll need
to install the latest .NET SDK separately.
If you prefer a different editor, you need to install the latest .NET SDK .
dotnet new creates an application. This command generates the files and assets
necessary for your application. The introduction to C# tutorials all use the console
application type. Once you've got the basics, you can expand to other application
types.
dotnet build builds the executable.
dotnet run runs the executable.
If you use Visual Studio 2019 for these tutorials, you'll choose a Visual Studio menu
selection when a tutorial directs you to run one of these CLI commands:
Numbers in C#
In the Numbers in C# tutorial, you'll learn how computers store numbers and how to
perform calculations with different numeric types. You'll learn the basics of rounding and
how to perform mathematical calculations using C#.
This tutorial assumes that you have finished the Hello world lesson.
This tutorial assumes that you have finished the Hello world and Numbers in C# lessons.
List collection
The List collection lesson gives you a tour of the List collection type that stores
sequences of data. You'll learn how to add and remove items, search for items, and sort
the lists. You'll explore different kinds of lists.
This tutorial assumes that you have finished the lessons listed above.
6 Collaborate with us on
GitHub .NET feedback
The source for this content can .NET is an open source project.
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
How to use integer and floating point
numbers in C#
Article • 10/15/2022
This tutorial teaches you about the numeric types in C#. You'll write small amounts of
code, then you'll compile and run that code. The tutorial contains a series of lessons that
explore numbers and math operations in C#. These lessons teach you the fundamentals
of the C# language.
Tip
To paste a code snippet inside the focus mode you should use your keyboard
shortcut ( Ctrl + v , or cmd + v ).
Prerequisites
The tutorial expects that you have a machine set up for local development. See Set up
your local environment for installation instructions and an overview of application
development in .NET.
If you don't want to set up a local environment, see the interactive-in-browser version of
this tutorial.
.NET CLI
) Important
The C# templates for .NET 6 use top level statements. Your application may not
match the code in this article, if you've already upgraded to the .NET 6. For more
information see the article on New C# templates generate top level statements
The .NET 6 SDK also adds a set of implicit global using directives for projects that
use the following SDKs:
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
These implicit global using directives include the most common namespaces for
the project type.
Open Program.cs in your favorite editor, and replace the contents of the file with the
following code:
C#
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);
You've seen one of the fundamental math operations with integers. The int type
represents an integer, a zero, positive, or negative whole number. You use the + symbol
for addition. Other common mathematical operations for integers include:
- for subtraction
* for multiplication
/ for division
Start by exploring those different operations. Add these lines after the line that writes
the value of c :
C#
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
You can also experiment by writing multiple mathematics operations in the same line, if
you'd like. Try c = a + b - 12 * 17; for example. Mixing variables and constant
numbers is allowed.
Tip
As you explore C# (or any programming language), you'll make mistakes when you
write code. The compiler will find those errors and report them to you. When the
output contains error messages, look closely at the example code and the code in
your window to see what to fix. That exercise will help you learn the structure of C#
code.
You've finished the first step. Before you start the next section, let's move the current
code into a separate method. A method is a series of statements grouped together and
given a name. You call a method by writing the method's name followed by () .
Organizing your code into methods makes it easier to start working with a new example.
When you finish, your code should look like this:
C#
WorkWithIntegers();
void WorkWithIntegers()
{
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
}
The line WorkWithIntegers(); invokes the method. The following code declares the
method and defines it.
C#
//WorkWithIntegers();
The // starts a comment in C#. Comments are any text you want to keep in your source
code but not execute as code. The compiler doesn't generate any executable code from
comments. Because WorkWithIntegers() is a method, you need to only comment out
one line.
The C# language defines the precedence of different mathematics operations with rules
consistent with the rules you learned in mathematics. Multiplication and division take
precedence over addition and subtraction. Explore that by adding the following code
after the call to WorkWithIntegers() , and executing dotnet run :
C#
int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);
The output demonstrates that the multiplication is performed before the addition.
You can force a different order of operation by adding parentheses around the
operation or operations you want performed first. Add the following lines and run again:
C#
d = (a + b) * c;
Console.WriteLine(d);
Explore more by combining many different operations. Add something like the following
lines. Try dotnet run again.
C#
d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);
You may have noticed an interesting behavior for integers. Integer division always
produces an integer result, even when you'd expect the result to include a decimal or
fractional portion.
C#
int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);
Before moving on, let's take all the code you've written in this section and put it in a
new method. Call that new method OrderPrecedence . Your code should look something
like this:
C#
// WorkWithIntegers();
OrderPrecedence();
void WorkWithIntegers()
{
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
}
void OrderPrecedence()
{
int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);
d = (a + b) * c;
Console.WriteLine(d);
d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);
int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);
}
C#
int a = 7;
int b = 4;
int c = 3;
int d = (a + b) / c;
int e = (a + b) % c;
Console.WriteLine($"quotient: {d}");
Console.WriteLine($"remainder: {e}");
The C# integer type differs from mathematical integers in one other way: the int type
has minimum and maximum limits. Add this code to see those limits:
C#
int max = int.MaxValue;
int min = int.MinValue;
Console.WriteLine($"The range of integers is {min} to {max}");
If a calculation produces a value that exceeds those limits, you have an underflow or
overflow condition. The answer appears to wrap from one limit to the other. Add these
two lines to see an example:
C#
Notice that the answer is very close to the minimum (negative) integer. It's the same as
min + 2 . The addition operation overflowed the allowed values for integers. The answer
is a very large negative number because an overflow "wraps around" from the largest
possible integer value to the smallest.
There are other numeric types with different limits and precision that you would use
when the int type doesn't meet your needs. Let's explore those other types next.
Before you start the next section, move the code you wrote in this section into a
separate method. Name it TestLimits .
C#
double a = 5;
double b = 4;
double c = 2;
double d = (a + b) / c;
Console.WriteLine(d);
Notice that the answer includes the decimal portion of the quotient. Try a slightly more
complicated expression with doubles:
C#
double e = 19;
double f = 23;
double g = 8;
double h = (e + f) / g;
Console.WriteLine(h);
The range of a double value is much greater than integer values. Try the following code
below what you've written so far:
C#
These values are printed in scientific notation. The number to the left of the E is the
significand. The number to the right is the exponent, as a power of 10. Just like decimal
numbers in math, doubles in C# can have rounding errors. Try this code:
C#
You know that 0.3 repeating finite number of times isn't exactly the same as 1/3 .
Challenge
Try other calculations with large numbers, small numbers, multiplication, and division
using the double type. Try more complicated calculations. After you've spent some time
with the challenge, take the code you've written and place it in a new method. Name
that new method WorkWithDoubles .
Notice that the range is smaller than the double type. You can see the greater precision
with the decimal type by trying the following code:
C#
double a = 1.0;
double b = 3.0;
Console.WriteLine(a / b);
decimal c = 1.0M;
decimal d = 3.0M;
Console.WriteLine(c / d);
The M suffix on the numbers is how you indicate that a constant should use the decimal
type. Otherwise, the compiler assumes the double type.
7 Note
The letter M was chosen as the most visually distinct letter between the double and
decimal keywords.
Notice that the math using the decimal type has more digits to the right of the decimal
point.
Challenge
Now that you've seen the different numeric types, write code that calculates the area of
a circle whose radius is 2.50 centimeters. Remember that the area of a circle is the radius
squared multiplied by PI. One hint: .NET contains a constant for PI, Math.PI that you can
use for that value. Math.PI, like all constants declared in the System.Math namespace, is
a double value. For that reason, you should use double instead of decimal values for
this challenge.
You should get an answer between 19 and 20. You can check your answer by looking at
the finished sample code on GitHub .
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
C# if statements and loops -
conditional logic tutorial
Article • 10/15/2022
This tutorial teaches you how to write C# code that examines variables and changes the
execution path based on those variables. You write C# code and see the results of
compiling and running it. The tutorial contains a series of lessons that explore branching
and looping constructs in C#. These lessons teach you the fundamentals of the C#
language.
Tip
To paste a code snippet inside the focus mode you should use your keyboard
shortcut ( Ctrl + v , or cmd + v ).
Prerequisites
The tutorial expects that you have a machine set up for local development. See Set up
your local environment for installation instructions and an overview of application
development in .NET.
If you prefer to run the code without having to set up a local environment, see the
interactive-in-browser version of this tutorial.
.NET CLI
) Important
The C# templates for .NET 6 use top level statements. Your application may not
match the code in this article, if you've already upgraded to the .NET 6. For more
information see the article on New C# templates generate top level statements
The .NET 6 SDK also adds a set of implicit global using directives for projects that
use the following SDKs:
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
These implicit global using directives include the most common namespaces for
the project type.
This command creates a new .NET console application in the current directory. Open
Program.cs in your favorite editor, and replace the contents with the following code:
C#
int a = 5;
int b = 6;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10.");
Try this code by typing dotnet run in your console window. You should see the message
"The answer is greater than 10." printed to your console. Modify the declaration of b so
that the sum is less than 10:
C#
int b = 3;
Type dotnet run again. Because the answer is less than 10, nothing is printed. The
condition you're testing is false. You don't have any code to execute because you've
only written one of the possible branches for an if statement: the true branch.
Tip
As you explore C# (or any programming language), you'll make mistakes when you
write code. The compiler will find and report the errors. Look closely at the error
output and the code that generated the error. The compiler error can usually help
you find the problem.
This first sample shows the power of if and Boolean types. A Boolean is a variable that
can have one of two values: true or false . C# defines a special type, bool for Boolean
variables. The if statement checks the value of a bool . When the value is true , the
statement following the if executes. Otherwise, it's skipped. This process of checking
conditions and executing statements based on those conditions is powerful.
C#
int a = 5;
int b = 3;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10");
else
Console.WriteLine("The answer is not greater than 10");
The statement following the else keyword executes only when the condition being
tested is false . Combining if and else with Boolean conditions provides all the power
you need to handle both a true and a false condition.
) Important
The indentation under the if and else statements is for human readers. The C#
language doesn't treat indentation or white space as significant. The statement
following the if or else keyword will be executed based on the condition. All the
samples in this tutorial follow a common practice to indent lines based on the
control flow of statements.
Because indentation isn't significant, you need to use { and } to indicate when you
want more than one statement to be part of the block that executes conditionally. C#
programmers typically use those braces on all if and else clauses. The following
example is the same as the one you created. Modify your code above to match the
following code:
C#
int a = 5;
int b = 3;
if (a + b > 10)
{
Console.WriteLine("The answer is greater than 10");
}
else
{
Console.WriteLine("The answer is not greater than 10");
}
Tip
Through the rest of this tutorial, the code samples all include the braces, following
accepted practices.
You can test more complicated conditions. Add the following code after the code you've
written so far:
C#
int c = 4;
if ((a + b + c > 10) && (a == b))
{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("And the first number is equal to the second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("Or the first number is not equal to the second");
}
The == symbol tests for equality. Using == distinguishes the test for equality from
assignment, which you saw in a = 5 .
The && represents "and". It means both conditions must be true to execute the
statement in the true branch. These examples also show that you can have multiple
statements in each conditional branch, provided you enclose them in { and } . You can
also use || to represent "or". Add the following code after what you've written so far:
C#
Modify the values of a , b , and c and switch between && and || to explore. You'll gain
more understanding of how the && and || operators work.
You've finished the first step. Before you start the next section, let's move the current
code into a separate method. That makes it easier to start working with a new example.
Put the existing code in a method called ExploreIf() . Call it from the top of your
program. When you finished those changes, your code should look like the following:
C#
ExploreIf();
void ExploreIf()
{
int a = 5;
int b = 3;
if (a + b > 10)
{
Console.WriteLine("The answer is greater than 10");
}
else
{
Console.WriteLine("The answer is not greater than 10");
}
int c = 4;
if ((a + b + c > 10) && (a > b))
{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("And the first number is greater than the
second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("Or the first number is not greater than the
second");
}
Comment out the call to ExploreIf() . It will make the output less cluttered as you work
in this section:
C#
//ExploreIf();
The // starts a comment in C#. Comments are any text you want to keep in your source
code but not execute as code. The compiler doesn't generate any executable code from
comments.
C#
int counter = 0;
while (counter < 10)
{
Console.WriteLine($"Hello World! The counter is {counter}");
counter++;
}
The while statement checks a condition and executes the statement or statement block
following the while . It repeatedly checks the condition, executing those statements until
the condition is false.
There's one other new operator in this example. The ++ after the counter variable is the
increment operator. It adds 1 to the value of counter and stores that value in the
counter variable.
) Important
Make sure that the while loop condition changes to false as you execute the code.
Otherwise, you create an infinite loop where your program never ends. That is not
demonstrated in this sample, because you have to force your program to quit using
CTRL-C or other means.
The while loop tests the condition before executing the code following the while . The
do ... while loop executes the code first, and then checks the condition. The do while
C#
int counter = 0;
do
{
Console.WriteLine($"Hello World! The counter is {counter}");
counter++;
} while (counter < 10);
This do loop and the earlier while loop produce the same output.
C#
The previous code does the same work as the while loop and the do loop you've
already used. The for statement has three parts that control how it works.
The first part is the for initializer: int index = 0; declares that index is the loop
variable, and sets its initial value to 0 .
The middle part is the for condition: index < 10 declares that this for loop continues
to execute as long as the value of counter is less than 10.
The final part is the for iterator: index++ specifies how to modify the loop variable after
executing the block following the for statement. Here, it specifies that index should be
incremented by 1 each time the block executes.
Experiment yourself. Try each of the following variations:
When you're done, let's move on to write some code yourself to use what you've
learned.
There's one other looping statement that isn't covered in this tutorial: the foreach
statement. The foreach statement repeats its statement for every item in a sequence of
items. It's most often used with collections, so it's covered in the next tutorial.
C#
C#
You can nest one loop inside the other to form pairs:
C#
You can see that the outer loop increments once for each full run of the inner loop.
Reverse the row and column nesting, and see the changes for yourself. When you're
done, place the code from this section in a method called ExploreLoops() .
Try it yourself. Then check how you did. You should get 63 for an answer. You can see
one possible answer by viewing the completed code on GitHub .
You can continue with the Arrays and collections tutorial in your own development
environment.
Selection statements
Iteration statements
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Learn to manage data collections using
List<T> in C#
Article • 03/07/2023
This introductory tutorial provides an introduction to the C# language and the basics of
the List<T> class.
Prerequisites
The tutorial expects that you have a machine set up for local development. See Set up
your local environment for installation instructions and an overview of application
development in .NET.
If you prefer to run the code without having to set up a local environment, see the
interactive-in-browser version of this tutorial.
) Important
The C# templates for .NET 6 use top level statements. Your application may not
match the code in this article, if you've already upgraded to the .NET 6. For more
information see the article on New C# templates generate top level statements
The .NET 6 SDK also adds a set of implicit global using directives for projects that
use the following SDKs:
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
These implicit global using directives include the most common namespaces for
the project type.
C#
Replace <name> with your name. Save Program.cs. Type dotnet run in your console
window to try it.
You've created a list of strings, added three names to that list, and printed the names in
all CAPS. You're using concepts that you've learned in earlier tutorials to loop through
the list.
The code to display names makes use of the string interpolation feature. When you
precede a string with the $ character, you can embed C# code in the string
declaration. The actual string replaces that C# code with the value it generates. In this
example, it replaces the {name.ToUpper()} with each name, converted to capital letters,
because you called the ToUpper method.
One important aspect of this List<T> type is that it can grow or shrink, enabling you to
add or remove elements. Add this code at the end of your program:
C#
Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
You've added two more names to the end of the list. You've also removed one as well.
Save the file, and type dotnet run to try it.
The List<T> enables you to reference individual items by index as well. You place the
index between [ and ] tokens following the list name. C# uses 0 for the first index. Add
this code directly below the code you just added and try it:
C#
You can't access an index beyond the end of the list. Remember that indices start at 0,
so the largest valid index is one less than the number of items in the list. You can check
how long the list is using the Count property. Add the following code at the end of your
program:
C#
Save the file, and type dotnet run again to see the results.
C#
The items in your list can be sorted as well. The Sort method sorts all the items in the list
in their normal order (alphabetically for strings). Add this code to the bottom of your
program:
C#
names.Sort();
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
Save the file and type dotnet run to try this latest version.
Before you start the next section, let's move the current code into a separate method.
That makes it easier to start working with a new example. Place all the code you've
written in a new method called WorkWithStrings() . Call that method at the top of your
program. When you finish, your code should look like this:
C#
WorkWithStrings();
void WorkWithStrings()
{
List<string> names = ["<name>", "Ana", "Felipe"];
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
Console.WriteLine($"My name is {names[0]}");
Console.WriteLine($"I've added {names[2]} and {names[3]} to the list");
names.Sort();
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
}
C#
That creates a list of integers, and sets the first two integers to the value 1. These are the
first two values of a Fibonacci Sequence, a sequence of numbers. Each next Fibonacci
number is found by taking the sum of the previous two numbers. Add this code:
C#
fibonacciNumbers.Add(previous + previous2);
Save the file and type dotnet run to see the results.
Tip
To concentrate on just this section, you can comment out the code that calls
WorkWithStrings(); . Just put two / characters in front of the call like this: //
WorkWithStrings(); .
Challenge
See if you can put together some of the concepts from this and earlier lessons. Expand
on what you've built so far with Fibonacci Numbers. Try to write the code to generate
the first 20 numbers in the sequence. (As a hint, the 20th Fibonacci number is 6765.)
Complete challenge
You can see an example solution by looking at the finished sample code on GitHub .
With each iteration of the loop, you're taking the last two integers in the list, summing
them, and adding that value to the list. The loop repeats until you've added 20 items to
the list.
Congratulations, you've completed the list tutorial. You can continue with additional
tutorials in your own development environment.
You can learn more about working with the List type in the .NET fundamentals article
on collections. You'll also learn about many other collection types.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
General Structure of a C# Program
Article • 08/01/2024
C# programs consist of one or more files. Each file contains zero or more namespaces. A
namespace contains types such as classes, structs, interfaces, enumerations, and
delegates, or other namespaces. The following example 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
{
}
enum YourEnum
{
}
namespace YourNestedNamespace
{
struct YourStruct
{
}
}
}
The preceding example uses top-level statements for the program's entry point. Only
one file can have top-level statements. The program's entry point is the first line of
program text in that file. You can also create a static method named Main as the
program's entry point, as shown in the following example:
C#
// A skeleton of a C# program
using System;
namespace YourNamespace
{
class YourClass
{
}
struct YourStruct
{
}
interface IYourInterface
{
}
enum YourEnum
{
}
namespace YourNestedNamespace
{
struct YourStruct
{
}
}
class Program
{
static void Main(string[] args)
{
//Your program starts here...
Console.WriteLine("Hello world!");
}
}
}
Related Sections
You learn about these program elements in the types section of the fundamentals guide:
Classes
Structs
Namespaces
Interfaces
Enums
Delegates
C# Language Specification
For more information, see Basic concepts in the C# Language Specification. The
language specification is the definitive source for C# syntax and usage.
6 Collaborate with us on
GitHub .NET feedback
The source for this content can .NET is an open source project.
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Main() and command-line arguments
Article • 06/22/2024
The Main method is the entry point of a C# application. When the application is started,
the Main method is the first method that is invoked.
There can only be one entry point in a C# program. If you have more than one class that
has a Main method, you must compile your program with the StartupObject compiler
option to specify which Main method to use as the entry point. For more information,
see StartupObject (C# Compiler Options).
C#
class TestClass
{
static void Main(string[] args)
{
// Display the number of command line arguments.
Console.WriteLine(args.Length);
}
}
You can also use Top-level statements in one file as the entry point for your application.
Just as the Main method, top-level statements can also return values and access
command-line arguments. For more information, see Top-level statements.
C#
using System.Text;
Console.WriteLine(builder.ToString());
Overview
The Main method is the entry point of an executable program; it is where the
program control starts and ends.
Main must be declared inside a class or struct. The enclosing class can be static .
Main must be static.
Main can either have a void , int , Task , or Task<int> return type.
If and only if Main returns a Task or Task<int> , the declaration of Main may
include the async modifier. This specifically excludes an async void Main method.
The Main method can be declared with or without a string[] parameter that
contains command-line arguments. When using Visual Studio to create Windows
applications, you can add the parameter manually or else use the
GetCommandLineArgs() method to obtain the command-line arguments.
Parameters are read as zero-indexed command-line arguments. Unlike C and C++,
the name of the program is not treated as the first command-line argument in the
args array, but it is the first element of the GetCommandLineArgs() method.
C#
The preceding examples don't specify an access modifier, so they're implicitly private
by default. That's typical, but it's possible to specify any explicit access modifier.
Tip
The addition of async and Task , Task<int> return types simplifies program code
when console applications need to start and await asynchronous operations in
Main .
ノ Expand table
If the return value from Main is not used, returning void or Task allows for slightly
simpler code.
ノ Expand table
The following example shows how the exit code for the process can be accessed.
This example uses .NET Core command-line tools. If you are unfamiliar with .NET Core
command-line tools, you can learn about them in this get-started article.
Create a new application by running dotnet new console . Modify the Main method in
Program.cs as follows:
C#
When a program is executed in Windows, any value returned from the Main function is
stored in an environment variable. This environment variable can be retrieved using
ERRORLEVEL from a batch file, or $LastExitCode from PowerShell.
You can build the application using the dotnet CLI dotnet build command.
Next, create a PowerShell script to run the application and display the result. Paste the
following code into a text file and save it as test.ps1 in the folder that contains the
project. Run the PowerShell script by typing test.ps1 at the PowerShell prompt.
Because the code returns zero, the batch file will report success. However, if you change
MainReturnValTest.cs to return a non-zero value and then recompile the program,
subsequent execution of the PowerShell script will report failure.
PowerShell
dotnet run
if ($LastExitCode -eq 0) {
Write-Host "Execution succeeded"
} else
{
Write-Host "Execution Failed"
}
Write-Host "Return value = " $LastExitCode
Output
Execution succeeded
Return value = 0
example. The code in the example ensures that your program runs until the
asynchronous operation is completed:
C#
class AsyncMainReturnValTest
{
public static int Main()
{
return AsyncConsoleWork().GetAwaiter().GetResult();
}
C#
class Program
{
static async Task<int> Main(string[] args)
{
return await AsyncConsoleWork();
}
An advantage of declaring Main as async is that the compiler always generates the
correct code.
When the application entry point returns a Task or Task<int> , the compiler generates a
new entry point that calls the entry point method declared in the application code.
Assuming that this entry point is called $GeneratedMain , the compiler generates the
following code for these entry points:
static Task Main() results in the compiler emitting the equivalent of private
Main(args).GetAwaiter().GetResult();
static Task<int> Main() results in the compiler emitting the equivalent of private
static int $GeneratedMain() => Main().GetAwaiter().GetResult();
Main(args).GetAwaiter().GetResult();
7 Note
If the examples used async modifier on the Main method, the compiler would
generate the same code.
Command-Line Arguments
You can send arguments to the Main method by defining the method in one of the
following ways:
ノ Expand table
If the arguments are not used, you can omit args from the method declaration for
slightly simpler code:
ノ Expand table
The parameter of the Main method is a String array that represents the command-line
arguments. Usually you determine whether arguments exist by testing the Length
property, for example:
C#
if (args.Length == 0)
{
System.Console.WriteLine("Please enter a numeric argument.");
return 1;
}
Tip
The args array can't be null. So, it's safe to access the Length property without null
checking.
You can also convert the string arguments to numeric types by using the Convert class
or the Parse method. For example, the following statement converts the string to a
long number by using the Parse method:
C#
C#
You can also use the Convert class method ToInt64 to do the same thing:
C#
Tip
To compile and run the application from a command prompt, follow these steps:
1. Paste the following code into any text editor, and then save the file as a text file
with the name Factorial.cs.
C#
class MainClass
{
static int Main(string[] args)
{
// Test if input arguments were supplied.
if (args.Length == 0)
{
Console.WriteLine("Please enter a numeric argument.");
Console.WriteLine("Usage: Factorial <num>");
return 1;
}
// Calculate factorial.
long result = Functions.Factorial(num);
// Print result.
if (result == -1)
Console.WriteLine("Input must be >= 0 and <= 20.");
else
Console.WriteLine($"The Factorial of {num} is {result}.");
return 0;
}
}
// If 3 is entered on command line, the
// output reads: The factorial of 3 is 6.
2. From the Start screen or Start menu, open a Visual Studio Developer Command
Prompt window, and then navigate to the folder that contains the file that you
created.
dotnet build
dotnet run -- 3
C# language specification
For more information, see the C# Language Specification. The language specification is
the definitive source for C# syntax and usage.
See also
System.Environment
How to display command line arguments
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Top-level statements - programs
without Main methods
Article • 02/29/2024
You don't have to explicitly include a Main method in a console application project.
Instead, you can use the top-level statements feature to minimize the code you have to
write.
Top-level statements allows you to write executable code directly at the root of a file,
eliminating the need for wrapping your code in a class or method. This means you can
create programs without the ceremony of a Program class and a Main method. In this
case, the compiler generates a Program class with an entry point method for the
application. The name of the generated method isn't Main , it's an implementation detail
that your code can't reference directly.
C#
Console.WriteLine("Hello World!");
Top-level statements let you write simple programs for small utilities such as Azure
Functions and GitHub Actions. They also make it simpler for new C# programmers to
get started learning and writing code.
The following sections explain the rules on what you can and can't do with top-level
statements.
A project can have any number of additional source code files that don't have top-level
statements.
No other entry points
You can write a Main method explicitly, but it can't function as an entry point. The
compiler issues the following warning:
CS7022 The entry point of the program is global code; ignoring 'Main()' entry point.
In a project with top-level statements, you can't use the -main compiler option to select
the entry point, even if the project has one or more Main methods.
using directives
If you include using directives, they must come first in the file, as in this example:
C#
using System.Text;
Console.WriteLine(builder.ToString());
Global namespace
Top-level statements are implicitly in the global namespace.
C#
MyClass.TestMethod();
MyNamespace.MyClass.MyMethod();
namespace MyNamespace
{
class MyClass
{
public static void MyMethod()
{
Console.WriteLine("Hello World from
MyNamespace.MyClass.MyMethod!");
}
}
}
args
Top-level statements can reference the args variable to access any command-line
arguments that were entered. The args variable is never null but its Length is zero if no
command-line arguments were provided. For example:
C#
if (args.Length > 0)
{
foreach (var arg in args)
{
Console.WriteLine($"Argument={arg}");
}
}
else
{
Console.WriteLine("No arguments");
}
await
You can call an async method by using await . For example:
C#
Console.Write("Hello ");
await Task.Delay(5000);
Console.WriteLine("World!");
C#
string? s = Console.ReadLine();
ノ Expand table
C# language specification
For more information, see the C# Language Specification. The language specification is
the definitive source for C# syntax and usage.
Feature specification - Top-level statements
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
The C# type system
Article • 11/14/2023
C# is a strongly typed language. Every variable and constant has a type, as does every
expression that evaluates to a value. Every method declaration specifies a name, the
type and kind (value, reference, or output) for each input parameter and for the return
value. The .NET class library defines built-in numeric types and complex types that
represent a wide variety of constructs. These include the file system, network
connections, collections and arrays of objects, and dates. A typical C# program uses
types from the class library and user-defined types that model the concepts that are
specific to the program's problem domain.
The compiler uses type information to make sure 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
7 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.
C#
// Declaration only:
float temperature;
string name;
MyClass myClass;
The types of method parameters and return values are specified in the method
declaration. The following signature shows a method that requires an int as an input
argument and returns a string:
C#
After you declare a variable, you can't redeclare it with a new type, and you can't assign
a value not compatible with its declared type. For example, you can't declare an int and
then assign it a Boolean value of true . However, values can be converted to other types,
for example when they're assigned to new variables or passed as method arguments. A
type conversion that doesn't cause data loss is performed automatically by the compiler.
A conversion that might cause data loss requires a cast in the source code.
Built-in types
C# provides a standard set of built-in types. These 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 types are available for you to use
in any C# program. For the complete list of the built-in types, see Built-in types.
Custom types
You use the struct, class, interface, enum, and record constructs to create your own
custom types. The .NET class library itself is a collection of custom types 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 that defines them. 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 Class Library.
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. These
types include all custom types in the .NET 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 or record keyword are reference types. Reference types and value types
The following illustration shows the relationship between value types and reference
types in the CTS.
7 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.
Classes and structs are two of the basic constructs of the common type system in .NET.
Each is essentially a data structure that encapsulates a set of data and behaviors that
belong together as a logical unit. The data and behaviors are the members of the class,
struct, or record. The members include its methods, properties, events, and so on, as
listed later in this article.
A class, struct, or record declaration is like a blueprint that is used to create instances or
objects at run time. If you define a class, struct, or record named Person , Person is the
name of the type. If you declare and initialize a variable p of type Person , p is said to
be an object or instance of Person . Multiple instances of the same Person type can be
created, and each instance can have different values in its properties and fields.
A class is a reference type. When an object of the type is created, the variable to which
the object is assigned holds only a reference to that memory. When the object reference
is assigned to a new variable, the new variable refers to the original object. Changes
made through one variable are reflected in the other variable because they both refer to
the same data.
A struct is a value type. When a struct is created, the variable to which the struct is
assigned holds the struct's actual data. When the struct is assigned to a new variable, it's
copied. The new variable and the original variable therefore contain two separate copies
of the same data. Changes made to one copy don't affect the other copy.
Record types may be either reference types ( record class ) or value types ( record
struct ). Record types contain methods that support value-equality.
In general, classes are used to model more complex behavior. Classes typically store
data that is intended to be modified after a class object is created. Structs are best
suited for small data structures. Structs typically store data that isn't intended to be
modified after the struct is created. Record types are data structures with additional
compiler synthesized members. Records typically store data that isn't intended to be
modified after the object is created.
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. The memory for a struct is allocated inline in whatever
context the variable is declared. There's no separate heap allocation or garbage
collection overhead for value-type variables. You can declare record struct types that
are value types and include the synthesized members for records.
The built-in numeric types are structs, and they have fields and methods that you can
access:
C#
But you declare and assign values to them as if they're simple non-aggregate types:
C#
byte num = 0xA;
int i = 5;
char c = 'Z';
Value types are sealed. You can't derive a type from any value type, for example
System.Int32. You can't 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 any interface type that it
implements. This cast 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 or any interface type 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#
For more information about structs, see Structure types. For more information about
value types, see Value types.
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 class library
contains a set of named constant integers that specify how a file should be opened. It's
defined as shown in the following example:
C#
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 class , record , delegate, array, or interface is a reference
type.
When declaring a variable of a reference type, it contains the value null until you assign
it with an instance of that type or create one using the new operator. Creation and
assignment of a class are demonstrated in the following example:
C#
An interface cannot be directly instantiated using the new operator. Instead, create and
assign an instance of a class that implements the interface. Consider the following
example:
C#
When the object is created, the memory is allocated on the managed heap. The variable
holds only a reference to the location of the object. Types on the managed heap require
overhead both when they're allocated and when they're reclaimed. Garbage collection is
the automatic memory management functionality of the CLR, which performs the
reclamation. However, garbage collection is also highly optimized, and in most scenarios
it doesn't 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.Array class. You declare and use them with the simplified syntax
that is provided by C#, as shown in the following example:
C#
Reference types fully support inheritance. When you create a class, you can inherit from
any other interface or class that isn't defined as sealed. Other classes can inherit from
your class and override your virtual methods. For more information about how to create
your own classes, see Classes, structs, and records. For more information about
inheritance and virtual methods, see Inheritance.
Because literals are typed, and all types derive ultimately from System.Object, you can
write and compile code such as the following code:
C#
C#
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 stringList object in the previous example. For more
information, see Generics.
It can be inconvenient to create a named type for simple sets of related values that you
don't 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 can't have a value of null. However, you can create nullable value
types by appending a ? after the type. For example, int? is an int type that can also
have the value null. Nullable value types are instances of the generic struct type
System.Nullable<T>. Nullable value types are especially useful when you're passing data
to and from databases in which numeric values might be null . For more information,
see Nullable value types.
Compile-time type and run-time type
A variable can have different compile-time and run-time types. The compile-time type is
the declared or inferred type of the variable in the source code. The run-time type is the
type of the instance referred to by that variable. Often those two types are the same, as
in the following example:
C#
In other cases, the compile-time type is different, as shown in the following two
examples:
C#
In both of the preceding examples, the run-time type is a string . The compile-time
type is object in the first line, and IEnumerable<char> in the second.
If the two types are different for a variable, it's important to understand when the
compile-time type and the run-time type apply. The compile-time type determines all
the actions taken by the compiler. These compiler actions include method call
resolution, overload resolution, and available implicit and explicit casts. The run-time
type determines all actions that are resolved at run time. These run-time actions include
dispatching virtual method calls, evaluating is and switch expressions, and other type
testing APIs. To better understand how your code interacts with types, recognize which
action applies to which type.
Related sections
For more information, see the following articles:
Builtin types
Value Types
Reference Types
C# language specification
For more information, see the C# Language Specification. The language specification is
the definitive source for C# syntax and usage.
6 Collaborate with us on
GitHub .NET feedback
The source for this content can .NET is an open source project.
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Declare namespaces to organize types
Article • 01/12/2022
Namespaces are heavily used in C# programming in two ways. First, .NET uses
namespaces to organize its many classes, as follows:
C#
System.Console.WriteLine("Hello World!");
System is a namespace and Console is a class in that namespace. The using keyword
can be used so that the complete name isn't required, as in the following example:
C#
using System;
C#
Console.WriteLine("Hello World!");
) Important
The C# templates for .NET 6 use top level statements. Your application may not
match the code in this article, if you've already upgraded to the .NET 6. For more
information see the article on New C# templates generate top level statements
The .NET 6 SDK also adds a set of implicit global using directives for projects that
use the following SDKs:
Microsoft.NET.Sdk
Microsoft.NET.Sdk.Web
Microsoft.NET.Sdk.Worker
These implicit global using directives include the most common namespaces for
the project type.
Second, declaring your own namespaces can help you control the scope of class and
method names in larger programming projects. Use the namespace keyword to declare
a namespace, as in the following example:
C#
namespace SampleNamespace
{
class SampleClass
{
public void SampleMethod()
{
System.Console.WriteLine(
"SampleMethod inside SampleNamespace");
}
}
}
Beginning with C# 10, you can declare a namespace for all types defined in that file, as
shown in the following example:
C#
namespace SampleNamespace;
class AnotherSampleClass
{
public void AnotherSampleMethod()
{
System.Console.WriteLine(
"SampleMethod inside SampleNamespace");
}
}
The advantage of this new syntax is that it's simpler, saving horizontal space and braces.
That makes your code easier to read.
Namespaces overview
Namespaces have the following properties:
C# language specification
For more information, see the Namespaces section of the C# language specification.
Introduction to classes
Article • 05/26/2023
Reference types
A type that is defined as a class 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 class by using the new operator, or assign it an object of a
compatible type that may have been created elsewhere, as shown in the following
example:
C#
//Declaring another object of the same type, assigning it the value of the
first object.
MyClass mc2 = mc;
When the object is created, enough memory is allocated on the managed heap for that
specific object, and the variable holds only a reference to the location of said object. The
memory used by an object is reclaimed by the automatic memory management
functionality of the CLR, which is known as garbage collection. For more information
about garbage collection, see Automatic memory management and garbage collection.
Declaring classes
Classes are declared by using the class keyword followed by a unique identifier, as
shown in the following example:
C#
An optional access modifier precedes the class keyword. Because public is used in this
case, anyone can create instances of this class. The name of the class follows the class
keyword. The name of the class must be a valid C# identifier name. The remainder of the
definition is the class body, where the behavior and data are defined. Fields, properties,
methods, and events on a class are collectively referred to as class members.
Creating objects
Although they're sometimes used interchangeably, a class and an object are different
things. A class defines a type of object, but it isn't an object itself. An object is a concrete
entity based on a class, and is sometimes referred to as an instance of a class.
Objects can be created by using the new keyword followed by the name of the class, like
this:
C#
When an instance of a class is created, a reference to the object is passed back to the
programmer. In the previous example, object1 is a reference to an object that is based
on Customer . This reference refers to the new object but doesn't contain the object data
itself. In fact, you can create an object reference without creating an object at all:
C#
Customer object2;
We don't recommend creating object references that don't refer to an object because
trying to access an object through such a reference fails at run time. A reference can be
made to refer to an object, either by creating a new object, or by assigning it an existing
object, such as this:
C#
This code creates two object references that both refer to the same object. Therefore,
any changes to the object made through object3 are reflected in subsequent uses of
object4 . Because objects that are based on classes are referred to by reference, classes
Every .NET type has a default value. Typically, that value is 0 for number types, and null
for all reference types. You can rely on that default value when it's reasonable in your
app.
When the .NET default isn't the right value, you can set an initial value using a field
initializer:
C#
You can require callers to provide an initial value by defining a constructor that's
responsible for setting that initial value:
C#
Beginning with C# 12, you can define a primary constructor as part of the class
declaration:
C#
Adding parameters to the class name defines the primary constructor. Those parameters
are available in the class body, which includes its members. You can use them to
initialize fields or anywhere else where they're needed.
You can also use the required modifier on a property and allow callers to use an object
initializer to set the initial value of the property:
C#
The addition of the required keyword mandates that callers must set those properties
as part of a new expression:
C#
Class inheritance
Classes fully support inheritance, a fundamental characteristic of object-oriented
programming. When you create a class, you can inherit from any other class that isn't
defined as sealed. Other classes can inherit from your class and override class virtual
methods. Furthermore, you can implement one or more interfaces.
C#
When a class declaration includes a base class, it inherits all the members of the base
class except the constructors. For more information, see Inheritance.
A class in C# can only directly inherit from one base class. However, because a base class
may itself inherit from another class, a class might indirectly inherit multiple base
classes. Furthermore, a class can directly implement one or more interfaces. For more
information, see Interfaces.
A class can be declared as abstract. An abstract class contains abstract methods that
have a signature definition but no implementation. Abstract classes can't be
instantiated. They can only be used through derived classes that implement the abstract
methods. By contrast, a sealed class doesn't allow other classes to derive from it. For
more information, see Abstract and Sealed Classes and Class Members.
Class definitions can be split between different source files. For more information, see
Partial Classes and Methods.
C# Language Specification
For more information, see the C# Language Specification. The language specification is
the definitive source for C# syntax and usage.
Introduction to record types in C#
Article • 05/26/2023
A record in C# is a class or struct that provides special syntax and behavior for working
with data models. The record modifier instructs the compiler to synthesize members
that are useful for types whose primary role is storing data. These members include an
overload of ToString() and members that support value equality.
Value equality
For records, value equality means that two variables of a record type are equal if the
types match and all property and field values compare equal. For other reference types
such as classes, equality means reference equality by default, unless value equality was
implemented. That is, two variables of a class type are equal if they refer to the same
object. Methods and operators that determine equality of two record instances use
value equality.
Not all data models work well with value equality. For example, Entity Framework Core
depends on reference equality to ensure that it uses only one instance of an entity type
for what is conceptually one entity. For this reason, record types aren't appropriate for
use as entity types in Entity Framework Core.
Immutability
An immutable type is one that prevents you from changing any property or field values
of an object after it's instantiated. Immutability can be useful when you need a type to
be thread-safe or you're depending on a hash code remaining the same in a hash table.
Records provide concise syntax for creating and working with immutable types.
Immutability isn't appropriate for all data scenarios. Entity Framework Core, for example,
doesn't support updating with immutable entity types.
How records differ from classes and structs
The same syntax that declares and instantiates classes or structs can be used with
records. Just substitute the class keyword with the record , or use record struct
instead of struct . Likewise, the same syntax for expressing inheritance relationships is
supported by record classes. Records differ from classes in the following ways:
Record structs differ from structs in that the compiler synthesizes the methods for
equality, and ToString . The compiler synthesizes a Deconstruct method for positional
record structs.
The compiler synthesizes a public init-only property for each primary constructor
parameter in a record class . In a record struct , the compiler synthesizes a public
read-write property. The compiler doesn't create properties for primary constructor
parameters in class and struct types that don't include record modifier.
Examples
The following example defines a public record that uses positional parameters to
declare and instantiate a record. It then prints the type name and property values:
C#
C#
person1.PhoneNumbers[0] = "555-1234";
Console.WriteLine(person1 == person2); // output: True
C#
C# Language Specification
For more information, see the C# Language Specification. The language specification is
the definitive source for C# syntax and usage.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Interfaces - define behavior for multiple
types
Article • 03/18/2023
By using interfaces, you can, for example, include behavior from multiple sources in a
class. That capability is important in C# because the language doesn't support multiple
inheritance of classes. In addition, you must use an interface if you want to simulate
inheritance for structs, because they can't actually inherit from another struct or class.
You define an interface by using the interface keyword as the following example shows.
C#
interface IEquatable<T>
{
bool Equals(T obj);
}
Any class or struct that implements the IEquatable<T> interface must contain a
definition for an Equals method that matches the signature that the interface specifies.
As a result, you can count on a class that implements IEquatable<T> to contain an
Equals method with which an instance of the class can determine whether it's equal to
another instance of the same class.
For more information about abstract classes, see Abstract and Sealed Classes and Class
Members.
implementation.
7 Note
When an interface declares static members, a type implementing that interface may
also declare static members with the same signature. Those are distinct and
uniquely identified by the type declaring the member. The static member declared
in a type doesn't override the static member declared in the interface.
A class or struct that implements an interface must provide an implementation for all
declared members without a default implementation provided by the interface.
However, if a base class implements an interface, any class that's derived from the base
class inherits that implementation.
C#
Properties and indexers of a class can define extra accessors for a property or indexer
that's defined in an interface. For example, an interface might declare a property that
has a get accessor. The class that implements the interface can declare the same
property with both a get and set accessor. However, if the property or indexer uses
explicit implementation, the accessors must match. For more information about explicit
implementation, see Explicit Interface Implementation and Interface Properties.
Interfaces can inherit from one or more interfaces. The derived interface inherits the
members from its base interfaces. A class that implements a derived interface must
implement all members in the derived interface, including all members of the derived
interface's base interfaces. That class may be implicitly converted to the derived
interface or any of its base interfaces. A class might include an interface multiple times
through base classes that it inherits or through interfaces that other interfaces inherit.
However, the class can provide an implementation of an interface only one time and
only if the class declares the interface as part of the definition of the class ( class
ClassName : InterfaceName ). If the interface is inherited because you inherited a base
class that implements the interface, the base class provides the implementation of the
members of the interface. However, the derived class can reimplement any virtual
interface members instead of using the inherited implementation. When interfaces
declare a default implementation of a method, any class implementing that interface
inherits that implementation (You need to cast the class instance to the interface type to
access the default implementation on the Interface member).
A base class can also implement interface members by using virtual members. In that
case, a derived class can change the interface behavior by overriding the virtual
members. For more information about virtual members, see Polymorphism.
Interfaces summary
An interface has the following properties:
In C# versions earlier than 8.0, an interface is like an abstract base class with only
abstract members. A class or struct that implements the interface must implement
all its members.
Beginning with C# 8.0, an interface may define default implementations for some
or all of its members. A class or struct that implements the interface doesn't have
to implement members that have default implementations. For more information,
see default interface methods.
An interface can't be instantiated directly. Its members are implemented by any
class or struct that implements the interface.
A class or struct can implement multiple interfaces. A class can inherit a base class
and also implement one or more interfaces.
Generic classes and methods
Article • 03/19/2024
Generics introduces the concept of type parameters to .NET. Generics make it possible
to design classes and methods that defer the specification of one or more type
parameters until you use the class or method in your code. For example, by using a
generic type parameter T , you can write a single class that other client code can use
without incurring the cost or risk of runtime casts or boxing operations, as shown here:
C#
Generic classes and methods combine reusability, type safety, and efficiency in a way
that their nongeneric counterparts can't. Generic type parameters are replaced with the
type arguments during compilation. In the preceding example, the compiler replaces T
with int . Generics are most frequently used with collections and the methods that
operate on them. The System.Collections.Generic namespace contains several generic-
based collection classes. The nongeneric collections, such as ArrayList aren't
recommended and are maintained only for compatibility purposes. For more
information, see Generics in .NET.
You can also create custom generic types and methods to provide your own generalized
solutions and design patterns that are type-safe and efficient. The following code
example shows a simple generic linked-list class for demonstration purposes. (In most
cases, you should use the List<T> class provided by .NET instead of creating your own.)
The type parameter T is used in several locations where a concrete type would
ordinarily be used to indicate the type of the item stored in the list:
C#
// constructor
public GenericList()
{
head = null;
}
The following code example shows how client code uses the generic GenericList<T>
class to create a list of integers. If you change the type argument, the following code
creates lists of strings or any other custom type:
C#
class TestGenericList
{
static void Main()
{
// int is the type argument
GenericList<int> list = new GenericList<int>();
Generic types aren't limited to classes. The preceding examples use class types,
but you can define generic interface and struct types, including record types.
Generics overview
Use generic types to maximize code reuse, type safety, and performance.
The most common use of generics is to create collection classes.
The .NET class library contains several generic collection classes in the
System.Collections.Generic namespace. The generic collections should be used
whenever possible instead of classes such as ArrayList in the System.Collections
namespace.
You can create your own generic interfaces, classes, methods, events, and
delegates.
Generic classes can be constrained to enable access to methods on particular data
types.
You can obtain information at run time on the types that are used in a generic data
type by using reflection.
C# language specification
For more information, see the C# Language Specification.
See also
Generics in .NET
System.Collections.Generic
6 Collaborate with us on
GitHub .NET feedback
The source for this content can .NET is an open source project.
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
Provide product feedback
more information, see our
contributor guide.
Anonymous types
Article • 11/29/2023
You create anonymous types by using the new operator together with an object
initializer. For more information about object initializers, see Object and Collection
Initializers.
The following example shows an anonymous type that is initialized with two properties
named Amount and Message .
C#
// Rest the mouse pointer over v.Amount and v.Message in the following
// statement to verify that their inferred types are int and string.
Console.WriteLine(v.Amount + v.Message);
Anonymous types are typically used in the select clause of a query expression to return
a subset of the properties from each object in the source sequence. For more
information about queries, see LINQ in C#.
Anonymous types contain one or more public read-only properties. No other kinds of
class members, such as methods or events, are valid. The expression that is used to
initialize a property cannot be null , an anonymous function, or a pointer type.
The most common scenario is to initialize an anonymous type with properties from
another type. In the following example, assume that a class exists that is named
Product . Class Product includes Color and Price properties, together with other
properties that you are not interested in. Variable products is a collection of Product
objects. The anonymous type declaration starts with the new keyword. The declaration
initializes a new type that uses only two properties from Product . Using anonymous
types causes a smaller amount of data to be returned in the query.
If you don't specify member names in the anonymous type, the compiler gives the
anonymous type members the same name as the property being used to initialize them.
You provide a name for a property that's being initialized with an expression, as shown
in the previous example. In the following example, the names of the properties of the
anonymous type are Color and Price .
C#
var productQuery =
from prod in products
select new { prod.Color, prod.Price };
Tip
You can use .NET style rule IDE0037 to enforce whether inferred or explicit member
names are preferred.
It is also possible to define a field by object of another type: class, struct or even another
anonymous type. It is done by using the variable holding this object just like in the
following example, where two anonymous types are created using already instantiated
user-defined types. In both cases the product field in the anonymous type shipment
and shipmentWithBonus will be of type Product containing its default values of each
field. And the bonus field will be of anonymous type created by the compiler.
C#
Typically, when you use an anonymous type to initialize a variable, you declare the
variable as an implicitly typed local variable by using var. The type name cannot be
specified in the variable declaration because only the compiler has access to the
underlying name of the anonymous type. For more information about var , see Implicitly
Typed Local Variables.
C#
var anonArray = new[] { new { name = "apple", diam = 4 }, new { name =
"grape", diam = 1 }};
Anonymous types are class types that derive directly from object, and that cannot be
cast to any type except object. The compiler provides a name for each anonymous type,
although your application cannot access it. From the perspective of the common
language runtime, an anonymous type is no different from any other reference type.
C#
You cannot declare a field, a property, an event, or the return type of a method as
having an anonymous type. Similarly, you cannot declare a formal parameter of a
method, property, constructor, or indexer as having an anonymous type. To pass an
anonymous type, or a collection that contains anonymous types, as an argument to a
method, you can declare the parameter as type object . However, using object for
anonymous types defeats the purpose of strong typing. If you must store query results
or pass them outside the method boundary, consider using an ordinary named struct or
class instead of an anonymous type.
Because the Equals and GetHashCode methods on anonymous types are defined in
terms of the Equals and GetHashCode methods of the properties, two instances of the
same anonymous type are equal only if all their properties are equal.
7 Note
Anonymous types do override the ToString method, concatenating the name and
ToString output of every property surrounded by curly braces.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
be found on GitHub, where you Select a link to provide feedback:
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Overview of object oriented techniques
in C#
Article • 09/21/2022
In C#, the definition of a type—a class, struct, or record—is like a blueprint that specifies
what the type can do. An object is basically a block of memory that has been allocated
and configured according to the blueprint. This article provides an overview of these
blueprints and their features. The next article in this series introduces objects.
Encapsulation
Encapsulation is sometimes referred to as the first pillar or principle of object-oriented
programming. A class or struct can specify how accessible each of its members is to
code outside of the class or struct. Methods and variables that aren't intended to be
used from outside of the class or assembly can be hidden to limit the potential for
coding errors or malicious exploits. For more information, see the Object-oriented
programming tutorial.
Members
The members of a type include all methods, fields, constants, properties, and events. In
C#, there are no global variables or methods as there are in some other languages. Even
a program's entry point, the Main method, must be declared within a class or struct
(implicitly when you use top-level statements).
The following list includes all the various kinds of members that may be declared in a
class, struct, or record.
Fields
Constants
Properties
Methods
Constructors
Events
Finalizers
Indexers
Operators
Nested Types
For more information, see Members.
Accessibility
Some methods and properties are meant to be called or accessed from code outside a
class or struct, known as client code. Other methods and properties might be only for
use in the class or struct itself. It's important to limit the accessibility of your code so
that only the intended client code can reach it. You specify how accessible your types
and their members are to client code by using the following access modifiers:
public
protected
internal
protected internal
private
private protected.
Inheritance
Classes (but not structs) support the concept of inheritance. A class that derives from
another class, called the base class, automatically contains all the public, protected, and
internal members of the base class except its constructors and finalizers.
Classes may be declared as abstract, which means that one or more of their methods
have no implementation. Although abstract classes cannot be instantiated directly, they
can serve as base classes for other classes that provide the missing implementation.
Classes can also be declared as sealed to prevent other classes from inheriting from
them.
Interfaces
Classes, structs, and records can implement multiple interfaces. To implement from an
interface means that the type implements all the methods defined in the interface. For
more information, see Interfaces.
Generic Types
Classes, structs, and records can be defined with one or more type parameters. Client
code supplies the type when it creates an instance of the type. For example, the List<T>
class in the System.Collections.Generic namespace is defined with one type parameter.
Client code creates an instance of a List<string> or List<int> to specify the type that
the list will hold. For more information, see Generics.
Static Types
Classes (but not structs or records) can be declared as static . A static class can contain
only static members and can't be instantiated with the new keyword. One copy of the
class is loaded into memory when the program loads, and its members are accessed
through the class name. Classes, structs, and records can contain static members. For
more information, see Static classes and static class members.
Nested Types
A class, struct, or record can be nested within another class, struct, or record. For more
information, see Nested Types.
Partial Types
You can define part of a class, struct, or method in one code file and another part in a
separate code file. For more information, see Partial Classes and Methods.
Object Initializers
You can instantiate and initialize class or struct objects, and collections of objects, by
assigning values to its properties. For more information, see How to initialize objects by
using an object initializer.
Anonymous Types
In situations where it isn't convenient or necessary to create a named class you use
anonymous types. Anonymous types are defined by their named data members. For
more information, see Anonymous types.
Extension Methods
You can "extend" a class without creating a derived class by creating a separate type.
That type contains methods that can be called as if they belonged to the original type.
For more information, see Extension methods.
Records
You can add the record modifier to a class or a struct. Records are types with built-in
behavior for value-based equality. A record (either record class or record struct )
provides the following features:
C# Language Specification
For more information, see the C# Language Specification. The language specification is
the definitive source for C# syntax and usage.
6 Collaborate with us on
GitHub .NET feedback
The source for this content can .NET is an open source project.
be found on GitHub, where you Select a link to provide feedback:
can also create and review
issues and pull requests. For Open a documentation issue
more information, see our
contributor guide. Provide product feedback
Objects - create instances of types
Article • 09/17/2021
A class or struct definition is like a blueprint that specifies what the type can do. An
object is basically a block of memory that has been allocated and configured according
to the blueprint. A program may create many objects of the same class. Objects are also
called instances, and they can be stored in either a named variable or in an array or
collection. Client code is the code that uses these variables to call the methods and
access the public properties of the object. In an object-oriented language such as C#, a
typical program consists of multiple objects interacting dynamically.
7 Note
Static types behave differently than what is described here. For more information,
see Static Classes and Static Class Members.
Instances of classes are created by using the new operator. In the following example,
Person is the type and person1 and person2 are instances, or objects, of that type.
C#
class Program
{
static void Main()
{
Person person1 = new Person("Leopold", 6);
Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name,
person1.Age);
Because structs are value types, a variable of a struct object holds a copy of the entire
object. Instances of structs can also be created by using the new operator, but this isn't
required, as shown in the following example:
C#
namespace Example;
The memory for both p1 and p2 is allocated on the thread stack. That memory is
reclaimed along with the type or method in which it's declared. This is one reason why
structs are copied on assignment. By contrast, the memory that is allocated for a class
instance is automatically reclaimed (garbage collected) by the common language
runtime when all references to the object have gone out of scope. It isn't possible to
deterministically destroy a class object like you can in C++. For more information about
garbage collection in .NET, see Garbage Collection.
7 Note
To determine whether the instance fields in two struct instances have the same
values, use the ValueType.Equals method. Because all structs implicitly inherit from
System.ValueType, you call the method directly on your object as shown in the
following example:
C#
if (p2.Equals(p1))
Console.WriteLine("p2 and p1 have the same values.");
To determine whether the values of the fields in two class instances are equal, you
might be able to use the Equals method or the == operator. However, only use
them if the class has overridden or overloaded them to provide a custom definition
of what "equality" means for objects of that type. The class might also implement
the IEquatable<T> interface or the IEqualityComparer<T> interface. Both
interfaces provide methods that can be used to test value equality. When
designing your own classes that override Equals , make sure to follow the
guidelines stated in How to define value equality for a type and
Object.Equals(Object).
Related Sections
For more information:
Classes
Constructors
Finalizers
Events
object
Inheritance
class
Structure types
new Operator
Common Type System
Inheritance - derive types to create
more specialized behavior
Article • 02/16/2022
Inheritance, together with encapsulation and polymorphism, is one of the three primary
characteristics of object-oriented programming. Inheritance enables you to create new
classes that reuse, extend, and modify the behavior defined in other classes. The class
whose members are inherited is called the base class, and the class that inherits those
members is called the derived class. A derived class can have only one direct base class.
However, inheritance is transitive. If ClassC is derived from ClassB , and ClassB is
derived from ClassA , ClassC inherits the members declared in ClassB and ClassA .
7 Note
Conceptually, a derived class is a specialization of the base class. For example, if you
have a base class Animal , you might have one derived class that is named Mammal and
another derived class that is named Reptile . A Mammal is an Animal , and a Reptile is an
Animal , but each derived class represents different specializations of the base class.
Interface declarations may define a default implementation for its members. These
implementations are inherited by derived interfaces, and by classes that implement
those interfaces. For more information on default interface methods, see the article on
interfaces.
When you define a class to derive from another class, the derived class implicitly gains
all the members of the base class, except for its constructors and finalizers. The derived
class reuses the code in the base class without having to reimplement it. You can add
more members in the derived class. The derived class extends the functionality of the
base class.
The following illustration shows a class WorkItem that represents an item of work in
some business process. Like all classes, it derives from System.Object and inherits all its
methods. WorkItem adds six members of its own. These members include a constructor,
because constructors aren't inherited. Class ChangeRequest inherits from WorkItem and
represents a particular kind of work item. ChangeRequest adds two more members to the
members that it inherits from WorkItem and from Object. It must add its own
constructor, and it also adds originalItemID . Property originalItemID enables the
ChangeRequest instance to be associated with the original WorkItem to which the change
request applies.
The following example shows how the class relationships demonstrated in the previous
illustration are expressed in C#. The example also shows how WorkItem overrides the
virtual method Object.ToString, and how the ChangeRequest class inherits the WorkItem
implementation of the method. The first block defines the classes:
C#
//Properties.
protected int ID { get; set; }
protected string Title { get; set; }
protected string Description { get; set; }
protected TimeSpan jobLength { get; set; }
// Method Update enables you to update the title and job length of an
// existing WorkItem object.
public void Update(string title, TimeSpan joblen)
{
this.Title = title;
this.jobLength = joblen;
}
This next block shows how to use the base and derived classes:
C#
Interfaces
An interface is a reference type that defines a set of members. All classes and structs
that implement that interface must implement that set of members. An interface may
define a default implementation for any or all of these members. A class can implement
multiple interfaces even though it can derive from only a single direct base class.
Interfaces are used to define specific capabilities for classes that don't necessarily have
an "is a" relationship. For example, the System.IEquatable<T> interface can be
implemented by any class or struct to determine whether two objects of the type are
equivalent (however the type defines equivalence). IEquatable<T> doesn't imply the
same kind of "is a" relationship that exists between a base class and a derived class (for
example, a Mammal is an Animal ). For more information, see Interfaces.
6 Collaborate with us on
GitHub .NET feedback
The source for this content can .NET is an open source project.
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Polymorphism
Article • 01/31/2023
At run time, objects of a derived class may be treated as objects of a base class in
places such as method parameters and collections or arrays. When this
polymorphism occurs, the object's declared type is no longer identical to its run-
time type.
Base classes may define and implement virtual methods, and derived classes can
override them, which means they provide their own definition and implementation.
At run-time, when client code calls the method, the CLR looks up the run-time type
of the object, and invokes that override of the virtual method. In your source code
you can call a method on a base class, and cause a derived class's version of the
method to be executed.
Virtual methods enable you to work with groups of related objects in a uniform way. For
example, suppose you have a drawing application that enables a user to create various
kinds of shapes on a drawing surface. You don't know at compile time which specific
types of shapes the user will create. However, the application has to keep track of all the
various types of shapes that are created, and it has to update them in response to user
mouse actions. You can use polymorphism to solve this problem in two basic steps:
1. Create a class hierarchy in which each specific shape class derives from a common
base class.
2. Use a virtual method to invoke the appropriate method on any derived class
through a single call to the base class method.
First, create a base class called Shape , and derived classes such as Rectangle , Circle ,
and Triangle . Give the Shape class a virtual method called Draw , and override it in each
derived class to draw the particular shape that the class represents. Create a
List<Shape> object and add a Circle , Triangle , and Rectangle to it.
C#
// Virtual method
public virtual void Draw()
{
Console.WriteLine("Performing base class drawing tasks");
}
}
To update the drawing surface, use a foreach loop to iterate through the list and call the
Draw method on each Shape object in the list. Even though each object in the list has a
declared type of Shape , it's the run-time type (the overridden version of the method in
each derived class) that will be invoked.
C#
In C#, every type is polymorphic because all types, including user-defined types, inherit
from Object.
Polymorphism overview
Virtual members
When a derived class inherits from a base class, it includes all the members of the base
class. All the behavior declared in the base class is part of the derived class. That enables
objects of the derived class to be treated as objects of the base class. Access modifiers
( public , protected , private and so on) determine if those members are accessible from
the derived class implementation. Virtual methods gives the designer different choices
for the behavior of the derived class:
The derived class may override virtual members in the base class, defining new
behavior.
The derived class may inherit the closest base class method without overriding it,
preserving the existing behavior but enabling further derived classes to override
the method.
The derived class may define new non-virtual implementation of those members
that hide the base class implementations.
A derived class can override a base class member only if the base class member is
declared as virtual or abstract. The derived member must use the override keyword to
explicitly indicate that the method is intended to participate in virtual invocation. The
following code provides an example:
C#
Fields can't be virtual; only methods, properties, events, and indexers can be virtual.
When a derived class overrides a virtual member, that member is called even when an
instance of that class is being accessed as an instance of the base class. The following
code provides an example:
C#
BaseClass A = B;
A.DoWork(); // Also calls the new method.
Virtual methods and properties enable derived classes to extend a base class without
needing to use the base class implementation of a method. For more information, see
Versioning with the Override and New Keywords. An interface provides another way to
define a method or set of methods whose implementation is left to derived classes.
C#
public class BaseClass
{
public void DoWork() { WorkField++; }
public int WorkField;
public int WorkProperty
{
get { return 0; }
}
}
Hidden base class members may be accessed from client code by casting the instance of
the derived class to an instance of the base class. For example:
C#
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
C#
public class A
{
public virtual void DoWork() { }
}
public class B : A
{
public override void DoWork() { }
}
A derived class can stop virtual inheritance by declaring an override as sealed. Stopping
inheritance requires putting the sealed keyword before the override keyword in the
class member declaration. The following code provides an example:
C#
public class C : B
{
public sealed override void DoWork() { }
}
In the previous example, the method DoWork is no longer virtual to any class derived
from C . It's still virtual for instances of C , even if they're cast to type B or type A . Sealed
methods can be replaced by derived classes by using the new keyword, as the following
example shows:
C#
public class D : C
{
public new void DoWork() { }
}
In this case, if DoWork is called on D using a variable of type D , the new DoWork is called.
If a variable of type C , B , or A is used to access an instance of D , a call to DoWork will
follow the rules of virtual inheritance, routing those calls to the implementation of
DoWork on class C .
C#
7 Note
It is recommended that virtual members use base to call the base class
implementation of that member in their own implementation. Letting the base class
behavior occur enables the derived class to concentrate on implementing behavior
specific to the derived class. If the base class implementation is not called, it is up
to the derived class to make their behavior compatible with the behavior of the
base class.
Pattern matching overview
Article • 03/13/2024
This article provides an overview of scenarios where you can use pattern matching.
These techniques can improve the readability and correctness of your code. For a full
discussion of all the patterns you can apply, see the article on patterns in the language
reference.
Null checks
One of the most common scenarios for pattern matching is to ensure values aren't
null . You can test and convert a nullable value type to its underlying type while testing
C#
The preceding code is a declaration pattern to test the type of the variable, and assign it
to a new variable. The language rules make this technique safer than many others. The
variable number is only accessible and assigned in the true portion of the if clause. If
you try to access it elsewhere, either in the else clause, or after the if block, the
compiler issues an error. Secondly, because you're not using the == operator, this
pattern works when a type overloads the == operator. That makes it an ideal way to
check null reference values, adding the not pattern:
C#
The preceding example used a constant pattern to compare the variable to null . The
not is a logical pattern that matches when the negated pattern doesn't match.
Type tests
Another common use for pattern matching is to test a variable to see if it matches a
given type. For example, the following code tests if a variable is non-null and
implements the System.Collections.Generic.IList<T> interface. If it does, it uses the
ICollection<T>.Count property on that list to find the middle index. The declaration
pattern doesn't match a null value, regardless of the compile-time type of the variable.
The code below guards against null , in addition to guarding against a type that doesn't
implement IList .
C#
The same tests can be applied in a switch expression to test a variable against multiple
different types. You can use that information to create better algorithms based on the
specific run-time type.
Compare discrete values
You can also test a variable to find a match on specific values. The following code shows
one example where you test a value against all possible values declared in an
enumeration:
C#
C#
The preceding example shows the same algorithm, but uses string values instead of an
enum. You would use this scenario if your application responds to text commands
instead of a regular data format. Starting with C# 11, you can also use a Span<char> or a
ReadOnlySpan<char> to test for constant string values, as shown in the following sample:
C#
In all these examples, the discard pattern ensures that you handle every input. The
compiler helps you by making sure every possible input value is handled.
Relational patterns
You can use relational patterns to test how a value compares to constants. For example,
the following code returns the state of water based on the temperature in Fahrenheit:
C#
The preceding code also demonstrates the conjunctive and logical pattern to check that
both relational patterns match. You can also use a disjunctive or pattern to check that
either pattern matches. The two relational patterns are surrounded by parentheses,
which you can use around any pattern for clarity. The final two switch arms handle the
cases for the melting point and the boiling point. Without those two arms, the compiler
warns you that your logic doesn't cover every possible input.
The preceding code also demonstrates another important feature the compiler provides
for pattern matching expressions: The compiler warns you if you don't handle every
input value. The compiler also issues a warning if the pattern for a switch arm is covered
by a previous pattern. That gives you freedom to refactor and reorder switch
expressions. Another way to write the same expression could be:
C#
The key lesson in the preceding sample, and any other refactoring or reordering, is that
the compiler validates that your code handles all possible inputs.
Multiple inputs
All the patterns covered so far have been checking one input. You can write patterns
that examine multiple properties of an object. Consider the following Order record:
C#
The preceding positional record type declares two members at explicit positions.
Appearing first is the Items , then the order's Cost . For more information, see Records.
The following code examines the number of items and the value of an order to calculate
a discounted price:
C#
The first two arms examine two properties of the Order . The third examines only the
cost. The next checks against null , and the final matches any other value. If the Order
type defines a suitable Deconstruct method, you can omit the property names from the
pattern and use deconstruction to examine properties:
C#
The preceding code demonstrates the positional pattern where the properties are
deconstructed for the expression.
List patterns
You can check elements in a list or an array using a list pattern. A list pattern provides a
means to apply a pattern to any element of a sequence. In addition, you can apply the
discard pattern ( _ ) to match any element, or apply a slice pattern to match zero or more
elements.
List patterns are a valuable tool when data doesn't follow a regular structure. You can
use pattern matching to test the shape and values of the data instead of transforming it
into a set of objects.
Consider the following excerpt from a text file containing bank transactions:
Output
It's a CSV format, but some of the rows have more columns than others. Even worse for
processing, one column in the WITHDRAWAL type contains user-generated text and can
contain a comma in the text. A list pattern that includes the discard pattern, constant
pattern, and var pattern to capture the value processes data in this format:
C#
The preceding example takes a string array, where each element is one field in the row.
The switch expression keys on the second field, which determines the kind of
transaction, and the number of remaining columns. Each row ensures the data is in the
correct format. The discard pattern ( _ ) skips the first field, with the date of the
transaction. The second field matches the type of transaction. Remaining element
matches skip to the field with the amount. The final match uses the var pattern to
capture the string representation of the amount. The expression calculates the amount
to add or subtract from the balance.
List patterns enable you to match on the shape of a sequence of data elements. You use
the discard and slice patterns to match the location of elements. You use other patterns
to match characteristics about individual elements.
This article provided a tour of the kinds of code you can write with pattern matching in
C#. The following articles show more examples of using patterns in scenarios, and the
full vocabulary of patterns available to use.
See also
Use pattern matching to avoid 'is' check followed by a cast (style rules IDE0020 and
IDE0038)
Exploration: Use pattern matching to build your class behavior for better code
Tutorial: Use pattern matching to build type-driven and data-driven algorithms
Reference: Pattern matching
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Discards - C# Fundamentals
Article • 11/14/2023
Discards are placeholder variables that are intentionally unused in application code.
Discards are equivalent to unassigned variables; they don't have a value. A discard
communicates intent to the compiler and others that read your code: You intended to
ignore the result of an expression. You may want to ignore the result of an expression,
one or more members of a tuple expression, an out parameter to a method, or the
target of a pattern matching expression.
Discards make the intent of your code clear. A discard indicates that our code never
uses the variable. They enhance its readability and maintainability.
You indicate that a variable is a discard by assigning it the underscore ( _ ) as its name.
For example, the following method call returns a tuple in which the first and second
values are discards. area is a previously declared variable set to the third component
returned by GetCityInformation :
C#
You can use discards to specify unused input parameters of a lambda expression. For
more information, see the Input parameters of a lambda expression section of the
Lambda expressions article.
C#
For more information on deconstructing tuples with discards, see Deconstructing tuples
and other types.
The Deconstruct method of a class, structure, or interface also allows you to retrieve
and deconstruct a specific set of data from an object. You can use discards when you're
interested in working with only a subset of the deconstructed values. The following
example deconstructs a Person object into four strings (the first and last names, the city,
and the state), but discards the last name and the state.
C#
using System;
namespace Discards
{
public class Person
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string State { get; set; }
C#
C#
A standalone discard
You can use a standalone discard to indicate any variable that you choose to ignore.
One typical use is to use an assignment to ensure that an argument isn't null. The
following code uses a discard to force an assignment. The right side of the assignment
uses the null coalescing operator to throw an System.ArgumentNullException when the
argument is null . The code doesn't need the result of the assignment, so it's discarded.
The expression forces a null check. The discard clarifies your intent: the result of the
assignment isn't needed or used.
C#
The following example uses a standalone discard to ignore the Task object returned by
an asynchronous operation. Assigning the task has the effect of suppressing the
exception that the operation throws as it is about to complete. It makes your intent
clear: You want to discard the Task , and ignore any errors generated from that
asynchronous operation.
C#
Without assigning the task to a discard, the following code generates a compiler
warning:
C#
7 Note
If you run either of the preceding two samples using a debugger, the debugger will
stop the program when the exception is thrown. Without a debugger attached, the
exception is silently ignored in both cases.
_ is also a valid identifier. When used outside of a supported context, _ is treated not
as a discard but as a valid variable. If an identifier named _ is already in scope, the use
of _ as a standalone discard can result in:
C#
C#
Compiler error CS0136, "A local or parameter named '_' cannot be declared in this
scope because that name is used in an enclosing local scope to define a local or
parameter." For example:
C#
public void DoSomething(int _)
{
var _ = GetValue(); // Error: cannot declare local _ when one is
already in scope
}
// The example displays the following compiler error:
// error CS0136:
// A local or parameter named '_' cannot be declared in this
scope
// because that name is used in an enclosing local scope
// to define a local or parameter
See also
Remove unnecessary expression value (style rule IDE0058)
Remove unnecessary value assignment (style rule IDE0059)
Remove unused parameter (style rule IDE0060)
Deconstructing tuples and other types
is operator
switch expression
6 Collaborate with us on
GitHub .NET feedback
The .NET documentation is open
The source for this content can
source. Provide feedback here.
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Deconstructing tuples and other types
Article • 09/29/2022
A tuple provides a lightweight way to retrieve multiple values from a method call. But
once you retrieve the tuple, you have to handle its individual elements. Working on an
element-by-element basis is cumbersome, as the following example shows. The
QueryCityData method returns a three-tuple, and each of its elements is assigned to a
C#
Retrieving multiple field and property values from an object can be equally
cumbersome: you must assign a field or property value to a variable on a member-by-
member basis.
You can retrieve multiple elements from a tuple or retrieve multiple field, property, and
computed values from an object in a single deconstruct operation. To deconstruct a
tuple, you assign its elements to individual variables. When you deconstruct an object,
you assign selected values to individual variables.
Tuples
C# features built-in support for deconstructing tuples, which lets you unpackage all the
items in a tuple in a single operation. The general syntax for deconstructing a tuple is
similar to the syntax for defining one: you enclose the variables to which each element is
to be assigned in parentheses in the left side of an assignment statement. For example,
the following statement assigns the elements of a four-tuple to four separate variables:
C#
You can explicitly declare the type of each field inside parentheses. The following
example uses this approach to deconstruct the three-tuple returned by the
QueryCityData method.
C#
You can use the var keyword so that C# infers the type of each variable. You place
the var keyword outside of the parentheses. The following example uses type
inference when deconstructing the three-tuple returned by the QueryCityData
method.
C#
You can also use the var keyword individually with any or all of the variable
declarations inside the parentheses.
C#
public static void Main()
{
(string city, var population, var area) = QueryCityData("New York
City");
Lastly, you may deconstruct the tuple into variables that have already been
declared.
C#
C#
You can't specify a specific type outside the parentheses even if every field in the tuple
has the same type. Doing so generates compiler error CS8136, "Deconstruction 'var (...)'
form disallows a specific type for 'var'.".
You must assign each element of the tuple to a variable. If you omit any elements, the
compiler generates error CS8132, "Can't deconstruct a tuple of 'x' elements into 'y'
variables."
The following example illustrates the use of tuples with discards. The
QueryCityDataForYears method returns a six-tuple with the name of a city, its area, a
year, the city's population for that year, a second year, and the city's population for that
second year. The example shows the change in population between those two years. Of
the data available from the tuple, we're unconcerned with the city area, and we know
the city name and the two dates at design-time. As a result, we're only interested in the
two population values stored in the tuple, and can handle its remaining values as
discards.
C#
using System;
User-defined types
C# doesn't offer built-in support for deconstructing non-tuple types other than the
record and DictionaryEntry types. However, as the author of a class, a struct, or an
interface, you can allow instances of the type to be deconstructed by implementing one
or more Deconstruct methods. The method returns void, and each value to be
deconstructed is indicated by an out parameter in the method signature. For example,
the following Deconstruct method of a Person class returns the first, middle, and last
name:
C#
public void Deconstruct(out string fname, out string mname, out string
lname)
You can then deconstruct an instance of the Person class named p with an assignment
like the following code:
C#
C#
using System;
public void Deconstruct(out string fname, out string mname, out string
lname)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
}
Multiple Deconstruct methods having the same number of parameters are ambiguous.
You must be careful to define Deconstruct methods with different numbers of
parameters, or "arity". Deconstruct methods with the same number of parameters
cannot be distinguished during overload resolution.
The following example deconstructs a Person object into four strings (the first and last
names, the city, and the state) but discards the last name and the state.
C#
The following example defines two Deconstruct extension methods for the
System.Reflection.PropertyInfo class. The first returns a set of values that indicate the
characteristics of the property, including its type, whether it's static or instance, whether
it's read-only, and whether it's indexed. The second indicates the property's accessibility.
Because the accessibility of get and set accessors can differ, Boolean values indicate
whether the property has separate get and set accessors and, if it does, whether they
have the same accessibility. If there's only one accessor or both the get and the set
accessor have the same accessibility, the access variable indicates the accessibility of
the property as a whole. Otherwise, the accessibility of the get and set accessors are
indicated by the getAccess and setAccess variables.
C#
using System;
using System.Collections.Generic;
using System.Reflection;
if (getter != null)
{
if (getter.IsPublic)
getAccessTemp = "public";
else if (getter.IsPrivate)
getAccessTemp = "private";
else if (getter.IsAssembly)
getAccessTemp = "internal";
else if (getter.IsFamily)
getAccessTemp = "protected";
else if (getter.IsFamilyOrAssembly)
getAccessTemp = "protected internal";
}
if (setter != null)
{
if (setter.IsPublic)
setAccessTemp = "public";
else if (setter.IsPrivate)
setAccessTemp = "private";
else if (setter.IsAssembly)
setAccessTemp = "internal";
else if (setter.IsFamily)
setAccessTemp = "protected";
else if (setter.IsFamilyOrAssembly)
setAccessTemp = "protected internal";
}
if (!hasGetAndSet | sameAccess)
{
Console.WriteLine(accessibility);
}
else
{
Console.WriteLine($"\n The get accessor: {getAccessibility}");
Console.WriteLine($" The set accessor: {setAccessibility}");
}
}
}
// The example displays the following output:
// The System.DateTime.Now property:
// PropertyType: DateTime
// Static: True
// Read-only: True
// Indexed: False
//
// Accessibility of the System.Collections.Generic.List`1.Item
property: public
C#
You can add a Deconstruct method to system types that don't have one. Consider the
following extension method:
C#
This extension method allows all Nullable<T> types to be deconstructed into a tuple of
(bool hasValue, T value) . The following example shows code that uses this extension
method:
C#
questionableDateTime = DateTime.Now;
(hasValue, value) = questionableDateTime;
Console.WriteLine(
$"{{ HasValue = {hasValue}, Value = {value} }}");
// Example outputs:
// { HasValue = False, Value = 1/1/0001 12:00:00 AM }
// { HasValue = True, Value = 11/10/2021 6:11:45 PM }
record types
When you declare a record type by using two or more positional parameters, the
compiler creates a Deconstruct method with an out parameter for each positional
parameter in the record declaration. For more information, see Positional syntax for
property definition and Deconstructor behavior in derived records.
See also
Deconstruct variable declaration (style rule IDE0042)
Discards
Tuple types
6 Collaborate with us on
GitHub .NET feedback
The .NET documentation is open
The source for this content can
source. Provide feedback here.
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Exceptions and Exception Handling
Article • 04/22/2023
The C# language's exception handling features help you deal with any unexpected or
exceptional situations that occur when a program is running. Exception handling uses
the try , catch , and finally keywords to try actions that may not succeed, to handle
failures when you decide that it's reasonable to do so, and to clean up resources
afterward. Exceptions can be generated by the common language runtime (CLR), by
.NET or third-party libraries, or by application code. Exceptions are created by using the
throw keyword.
In many cases, an exception may be thrown not by a method that your code has called
directly, but by another method further down in the call stack. When an exception is
thrown, the CLR will unwind the stack, looking for a method with a catch block for the
specific exception type, and it will execute the first such catch block that it finds. If it
finds no appropriate catch block anywhere in the call stack, it will terminate the process
and display a message to the user.
In this example, a method tests for division by zero and catches the error. Without the
exception handling, this program would terminate with a DivideByZeroException was
unhandled error.
C#
try
{
result = SafeDivision(a, b);
Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
}
catch (DivideByZeroException)
{
Console.WriteLine("Attempted divide by zero.");
}
}
}
Exceptions Overview
Exceptions have the following properties:
C# Language Specification
For more information, see Exceptions in the C# Language Specification. The language
specification is the definitive source for C# syntax and usage.
See also
System.Exception
Exception-handling statements
Exceptions
Use exceptions
Article • 09/15/2021
In C#, errors in the program at run time are propagated through the program by using a
mechanism called exceptions. Exceptions are thrown by code that encounters an error
and caught by code that can correct the error. Exceptions can be thrown by the .NET
runtime or by code in a program. Once an exception is thrown, it propagates up the call
stack until a catch statement for the exception is found. Uncaught exceptions are
handled by a generic exception handler provided by the system that displays a dialog
box.
Exceptions are represented by classes derived from Exception. This class identifies the
type of exception and contains properties that have details about the exception.
Throwing an exception involves creating an instance of an exception-derived class,
optionally configuring properties of the exception, and then throwing the object by
using the throw keyword. For example:
C#
After an exception is thrown, the runtime checks the current statement to see whether it
is within a try block. If it is, any catch blocks associated with the try block are checked
to see whether they can catch the exception. Catch blocks typically specify exception
types; if the type of the catch block is the same type as the exception, or a base class of
the exception, the catch block can handle the method. For example:
C#
try
{
TestThrow();
}
catch (CustomException ex)
{
System.Console.WriteLine(ex.ToString());
}
If the statement that throws an exception isn't within a try block or if the try block
that encloses it has no matching catch block, the runtime checks the calling method for
a try statement and catch blocks. The runtime continues up the calling stack,
searching for a compatible catch block. After the catch block is found and executed,
control is passed to the next statement after that catch block.
A try statement can contain more than one catch block. The first catch statement that
can handle the exception is executed; any following catch statements, even if they're
compatible, are ignored. Order catch blocks from most specific (or most-derived) to
least specific. For example:
C#
using System;
using System.IO;
namespace Exceptions
{
public class CatchOrder
{
public static void Main()
{
try
{
using (var sw = new StreamWriter("./test.txt"))
{
sw.WriteLine("Hello");
}
}
// Put the more specific exceptions first.
catch (DirectoryNotFoundException ex)
{
Console.WriteLine(ex);
}
catch (FileNotFoundException ex)
{
Console.WriteLine(ex);
}
// Put the least specific exception last.
catch (IOException ex)
{
Console.WriteLine(ex);
}
Console.WriteLine("Done");
}
}
}
Before the catch block is executed, the runtime checks for finally blocks. Finally
blocks enable the programmer to clean up any ambiguous state that could be left over
from an aborted try block, or to release any external resources (such as graphics
handles, database connections, or file streams) without waiting for the garbage collector
in the runtime to finalize the objects. For example:
C#
try
{
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Closing the file allows you to reopen it immediately - otherwise
IOException is thrown.
file?.Close();
}
try
{
file = fileInfo.OpenWrite();
Console.WriteLine("OpenWrite() succeeded");
}
catch (IOException)
{
Console.WriteLine("OpenWrite() failed");
}
}
If WriteByte() threw an exception, the code in the second try block that tries to
reopen the file would fail if file.Close() isn't called, and the file would remain locked.
Because finally blocks are executed even if an exception is thrown, the finally block
in the previous example allows for the file to be closed correctly and helps avoid an
error.
If no compatible catch block is found on the call stack after an exception is thrown, one
of three things occurs:
If the exception is within a finalizer, the finalizer is aborted and the base finalizer, if
any, is called.
If the call stack contains a static constructor, or a static field initializer, a
TypeInitializationException is thrown, with the original exception assigned to the
InnerException property of the new exception.
If the start of the thread is reached, the thread is terminated.
Exception Handling (C# Programming
Guide)
Article • 03/14/2023
C#
try
{
// Code to try goes here.
}
catch (SomeSpecificException ex)
{
// Code to handle the exception goes here.
// Only catch exceptions that you know how to handle.
// Never catch base class System.Exception without
// rethrowing it at the end of the catch block.
}
C#
try
{
// Code to try goes here.
}
finally
{
// Code to execute after the try block goes here.
}
C#
try
{
// Code to try goes here.
}
catch (SomeSpecificException ex)
{
// Code to handle the exception goes here.
}
finally
{
// Code to execute after the try (and possibly catch) blocks
// goes here.
}
Catch Blocks
A catch block can specify the type of exception to catch. The type specification is called
an exception filter. The exception type should be derived from Exception. In general,
don't specify Exception as the exception filter unless either you know how to handle all
exceptions that might be thrown in the try block, or you've included a throw statement
at the end of your catch block.
Multiple catch blocks with different exception classes can be chained together. The
catch blocks are evaluated from top to bottom in your code, but only one catch block
is executed for each exception that is thrown. The first catch block that specifies the
exact type or a base class of the thrown exception is executed. If no catch block
specifies a matching exception class, a catch block that doesn't have any type is
selected, if one is present in the statement. It's important to position catch blocks with
the most specific (that is, the most derived) exception classes first.
You have a good understanding of why the exception might be thrown, and you
can implement a specific recovery, such as prompting the user to enter a new file
name when you catch a FileNotFoundException object.
You can create and throw a new, more specific exception.
C#
You want to partially handle an exception before passing it on for more handling.
In the following example, a catch block is used to add an entry to an error log
before rethrowing the exception.
C#
try
{
// Try to access a resource.
}
catch (UnauthorizedAccessException e)
{
// Call a custom error logging procedure.
LogError(e);
// Re-throw the error.
throw;
}
You can also specify exception filters to add a boolean expression to a catch clause.
Exception filters indicate that a specific catch clause matches only when that condition is
true. In the following example, both catch clauses use the same exception class, but an
extra condition is checked to create a different error message:
C#
An exception filter that always returns false can be used to examine all exceptions but
not process them. A typical use is to log exceptions:
C#
The LogException method always returns false , no catch clause using this exception
filter matches. The catch clause can be general, using System.Exception, and later
clauses can process more specific exception classes.
Finally Blocks
A finally block enables you to clean up actions that are performed in a try block. If
present, the finally block executes last, after the try block and any matched catch
block. A finally block always runs, whether an exception is thrown or a catch block
matching the exception type is found.
The finally block can be used to release resources such as file streams, database
connections, and graphics handles without waiting for the garbage collector in the
runtime to finalize the objects.
In the following example, the finally block is used to close a file that is opened in the
try block. Notice that the state of the file handle is checked before the file is closed. If
the try block can't open the file, the file handle still has the value null and the finally
block doesn't try to close it. Instead, if the file is opened successfully in the try block,
the finally block closes the open file.
C#
C# Language Specification
For more information, see Exceptions and The try statement in the C# Language
Specification. The language specification is the definitive source for C# syntax and
usage.
See also
C# reference
Exception-handling statements
using statement
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Create and throw exceptions
Article • 11/03/2023
Exceptions are used to indicate that an error has occurred while running the program.
Exception objects that describe an error are created and then thrown with the throw
statement or expression. The runtime then searches for the most compatible exception
handler.
Programmers should throw exceptions when one or more of the following conditions
are true:
The method can't complete its defined functionality. For example, if a parameter to
a method has an invalid value:
C#
An inappropriate call to an object is made, based on the object state. One example
might be trying to write to a read-only file. In cases where an object state doesn't
allow an operation, throw an instance of InvalidOperationException or an object
based on a derivation of this class. The following code is an example of a method
that throws an InvalidOperationException object:
C#
C#
7 Note
The preceding example shows how to use the InnerException property. It's
intentionally simplified. In practice, you should check that an index is in range
before using it. You could use this technique of wrapping an exception when a
member of a parameter throws an exception you couldn't anticipate before
calling the member.
Exceptions contain a property named StackTrace. This string contains the name of the
methods on the current call stack, together with the file name and line number where
the exception was thrown for each method. A StackTrace object is created automatically
by the common language runtime (CLR) from the point of the throw statement, so that
exceptions must be thrown from the point where the stack trace should begin.
All exceptions contain a property named Message. This string should be set to explain
the reason for the exception. Information that is sensitive to security shouldn't be put in
the message text. In addition to Message, ArgumentException contains a property
named ParamName that should be set to the name of the argument that caused the
exception to be thrown. In a property setter, ParamName should be set to value .
Public and protected methods throw exceptions whenever they can't complete their
intended functions. The exception class thrown is the most specific exception available
that fits the error conditions. These exceptions should be documented as part of the
class functionality, and derived classes or updates to the original class should retain the
same behavior for backward compatibility.
We recommend that you validate arguments and throw any corresponding exceptions,
such as ArgumentException and ArgumentNullException, before entering the
asynchronous parts of your methods. That is, these validation exceptions should emerge
synchronously before the work starts. The following code snippet shows an example
where, if the exceptions are thrown, the ArgumentException exceptions would emerge
synchronously, whereas the InvalidOperationException would be stored in the returned
task.
C#
if (toastTime < 1)
{
throw new ArgumentException(
"Toast time is too short.", nameof(toastTime));
}
Console.WriteLine("Toast is ready!");
C#
[Serializable]
public class InvalidDepartmentException : Exception
{
public InvalidDepartmentException() : base() { }
public InvalidDepartmentException(string message) : base(message) { }
public InvalidDepartmentException(string message, Exception inner) :
base(message, inner) { }
}
Add new properties to the exception class when the data they provide is useful to
resolving the exception. If new properties are added to the derived exception class,
ToString() should be overridden to return the added information.
C# language specification
For more information, see Exceptions and The throw statement in the C# Language
Specification. The language specification is the definitive source for C# syntax and
usage.
See also
Exception Hierarchy
6 Collaborate with us on
GitHub .NET feedback
The source for this content can The .NET documentation is open
source. Provide feedback here.
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Compiler-generated exceptions
Article • 04/22/2023
Some exceptions are thrown automatically by the .NET runtime when basic operations
fail. These exceptions and their error conditions are listed in the following table.
Exception Description
ArrayTypeMismatchException Thrown when an array can't store a given element because the
actual type of the element is incompatible with the actual type of
the array.
See also
Exception-handling statements
C# identifier naming rules and
conventions
Article • 12/15/2023
An identifier is the name you assign to a type (class, interface, struct, delegate, or
enum), member, variable, or namespace.
Naming rules
Valid identifiers must follow these rules. The C# compiler produces an error for any
identifier that doesn't follow these rules:
You can declare identifiers that match C# keywords by using the @ prefix on the
identifier. The @ isn't part of the identifier name. For example, @if declares an identifier
named if . These verbatim identifiers are primarily for interoperability with identifiers
declared in other languages.
For a complete definition of valid identifiers, see the Identifiers article in the C#
Language Specification.
) Important
The C# language specification only allows letter (Lu, Ll, Lt, Lm, Lo or Nl), digit (Nd),
connecting (Pc), combining (Mn or Mc), and formatting (Cf) categories. Anything
outside that is automatically replaced using _ . This might impact certain Unicode
characters.
Naming conventions
In addition to the rules, conventions for identifier names are used throughout the .NET
APIs. These conventions provide consistency for names, but the compiler doesn't
enforce them. You're free to use different conventions in your projects.
By convention, C# programs use PascalCase for type names, namespaces, and all public
members. In addition, the dotnet/docs team uses the following conventions, adopted
from the .NET Runtime team's coding style :
Enum types use a singular noun for nonflags, and a plural noun for flags.
Use meaningful and descriptive names for variables, methods, and classes.
Use PascalCase for constant names, both fields and local constants.
Private instance fields start with an underscore ( _ ) and the remaining text is
camelCased.
Static fields start with s_ . This convention isn't the default Visual Studio behavior,
nor part of the Framework design guidelines, but is configurable in editorconfig.
Avoid using abbreviations or acronyms in names, except for widely known and
accepted abbreviations.
Use meaningful and descriptive namespaces that follow the reverse domain name
notation.
Choose assembly names that represent the primary purpose of the assembly.
Avoid using single-letter names, except for simple loop counters. Also, syntax
examples that describe the syntax of C# constructs often use the following single-
letter names that match the convention used in the C# language specification.
Syntax examples are an exception to the rule.
Use S for structs, C for classes.
Use M for methods.
Use v for variables, p for parameters.
Use r for ref parameters.
Tip
You can enforce naming conventions that concern capitalization, prefixes, suffixes,
and word separators by using code-style naming rules.
Pascal case
Use pascal casing ("PascalCasing") when naming a class , interface , struct , or
delegate type.
C#
C#
C#
C#
When naming an interface , use pascal casing in addition to prefixing the name with an
I . This prefix clearly indicates to consumers that it's an interface .
C#
public interface IWorkerQueue
{
}
When naming public members of types, such as fields, properties, events, use pascal
casing. Also, use pascal casing for all methods and local functions.
C#
// An init-only property
public IWorkerQueue WorkerQueue { get; init; }
// An event
public event Action EventProcessing;
// Method
public void StartEventProcessing()
{
// Local function
static int CountQueueItems() => WorkerQueue.Count;
// ...
}
}
When writing positional records, use pascal casing for parameters as they're the public
properties of the record.
C#
For more information on positional records, see Positional syntax for property definition.
Camel case
Use camel casing ("camelCasing") when naming private or internal fields and prefix
them with _ . Use camel casing when naming local variables, including instances of a
delegate type.
C#
Tip
When editing C# code that follows these naming conventions in an IDE that
supports statement completion, typing _ will show all of the object-scoped
members.
When working with static fields that are private or internal , use the s_ prefix and
for thread static use t_ .
C#
[ThreadStatic]
private static TimeSpan t_timeSpan;
}
C#
For more information on C# naming conventions, see the .NET Runtime team's coding
style .
./snippets/coding-conventions
Consider using T as the type parameter name for types with one single letter type
parameter.
./snippets/coding-conventions
./snippets/coding-conventions
The code analysis rule CA1715 can be used to ensure that type parameters are named
appropriately.
C#
You don't have to change the names of objects that were created by using the
Visual Studio designer tools to make them fit other guidelines.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Common C# code conventions
Article • 08/01/2023
Coding conventions are essential for maintaining code readability, consistency, and
collaboration within a development team. Code that follows industry practices and
established guidelines is easier to understand, maintain, and extend. Most projects
enforce a consistent style through code conventions. The dotnet/docs and
dotnet/samples projects are no exception. In this series of articles, you learn our
coding conventions and the tools we use to enforce them. You can take our conventions
as-is, or modify them to suit your team's needs.
1. Correctness: Our samples are copied and pasted into your applications. We expect
that, so we need to make code that's resilient and correct, even after multiple edits.
2. Teaching: The purpose of our samples is to teach all of .NET and C#. For that
reason, we don't place restrictions on any language feature or API. Instead, those
samples teach when a feature is a good choice.
3. Consistency: Readers expect a consistent experience across our content. All
samples should conform to the same style.
4. Adoption: We aggressively update our samples to use new language features. That
practice raises awareness of new features, and makes them more familiar to all C#
developers.
) Important
The teaching and adoption goals are why the docs coding convention differs from
the runtime and compiler conventions. Both the runtime and compiler have strict
performance metrics for hot paths. Many other applications don't. Our teaching
goal mandates that we don't prohibit any construct. Instead, samples show when
constructs should be used. We update samples more aggressively than most
production applications do. Our adoption goal mandates that we show code you
should write today, even when code written last year doesn't need changes.
This article explains our guidelines. The guidelines have evolved over time, and you'll
find samples that don't follow our guidelines. We welcome PRs that bring those samples
into compliance, or issues that draw our attention to samples we should update. Our
guidelines are Open Source and we welcome PRs and issues. However, if your
submission would change these recommendations, open an issue for discussion first.
You're welcome to use our guidelines, or adapt them to your needs.
These tools make it easier for your team to adopt your preferred guidelines. Visual
Studio applies the rules in all .editorconfig files in scope to format your code. You can
use multiple configurations to enforce corporate-wide conventions, team conventions,
and even granular project conventions.
Code analysis produces warnings and diagnostics when the enabled rules are violated.
You configure the rules you want applied to your project. Then, each CI build notifies
developers when they violate any of the rules.
Diagnostic IDs
Choose appropriate diagnostic IDs when building your own analyzers
Language guidelines
The following sections describe practices that the .NET docs team follows to prepare
code examples and samples. In general, follow these practices:
String data
Use string interpolation to concatenate short strings, as shown in the following
code.
C#
To append strings in loops, especially when you're working with large amounts of
text, use a System.Text.StringBuilder object.
C#
var phrase =
"lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
var manyPhrases = new StringBuilder();
for (var i = 0; i < 10000; i++)
{
manyPhrases.Append(phrase);
}
//Console.WriteLine("tra" + manyPhrases);
Arrays
Use the concise syntax when you initialize arrays on the declaration line. In the
following example, you can't use var instead of string[] .
C#
C#
Delegates
Use Func<> and Action<> instead of defining delegate types. In a class, define the
delegate method.
C#
Call the method using the signature defined by the Func<> or Action<> delegate.
C#
If you create instances of a delegate type, use the concise syntax. In a class, define
the delegate type and a method that has a matching signature.
C#
public delegate void Del(string message);
Create an instance of the delegate type and call it. The following declaration shows
the condensed syntax.
C#
C#
C#
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.
In the following example, the try-finally statement only calls Dispose in the
finally block.
C#
C#
C#
Use && instead of & and || instead of | when you perform comparisons, as shown
in the following example.
C#
If the divisor is 0, the second clause in the if statement would cause a run-time error.
But the && operator short-circuits when the first expression is false. That is, it doesn't
evaluate the second expression. The & operator would evaluate both, resulting in a run-
time error when divisor is 0.
new operator
Use one of the concise forms of object instantiation, as shown in the following
declarations.
C#
C#
C#
C#
C#
Event handling
Use a lambda expression to define an event handler that you don't need to
remove later:
C#
public Form2()
{
this.Click += (s, e) =>
{
MessageBox.Show(
((MouseEventArgs)e).Location.ToString());
};
}
C#
public Form1()
{
this.Click += new EventHandler(Form1_Click);
}
Static members
Call static members by using the class name: ClassName.StaticMember. This practice
makes code more readable by making static access clear. Don't 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#
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 customer in customers
join distributor in distributors on customer.City equals
distributor.City
select new { CustomerName = customer.Name, DistributorID =
distributor.ID };
Use implicit typing in the declaration of query variables and range variables. This
guidance on implicit typing in LINQ queries overrides the general rules for
implicitly typed local variables. LINQ queries often use projections that create
anonymous types. Other query expressions create results with nested generic
types. Implicit typed variables are often more readable.
C#
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#
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#
C#
Don't use var when the type isn't apparent from the right side of the assignment.
Don't assume the type is clear from a method name. A variable type is considered
clear if it's a new operator, an explicit cast or assignment to a literal value.
C#
Don't use variable names to specify the type of the variable. It might not be
correct. Instead, use the type to specify the type, and use the variable name to
indicate the semantic information of the variable. The following example should
use string for the type and something like iterations to indicate the meaning of
the information read from the console.
C#
Avoid the use of var in place of dynamic. Use dynamic when you want run-time
type inference. For more information, see Using type dynamic (C# Programming
Guide).
C#
var phrase =
"lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
var manyPhrases = new StringBuilder();
for (var i = 0; i < 10000; i++)
{
manyPhrases.Append(phrase);
}
//Console.WriteLine("tra" + manyPhrases);
Don't use implicit typing to determine the type of the loop variable in foreach
loops. In most cases, the type of elements in the collection isn't immediately
obvious. The collection's name shouldn't be solely relied upon for inferring the
type of its elements.
C#
use implicit type for the result sequences in LINQ queries. The section on LINQ
explains that many LINQ queries result in anonymous types where implicit types
must be used. Other queries result in nested generic types where var is more
readable.
7 Note
Some of our samples explain the natural type of an expression. Those samples must use
var so that the compiler picks the natural type. Even though those examples are less
obvious, the use of var is required for the sample. The text should explain the behavior.
C#
using Azure;
namespace CoolStuff.AwesomeFeature
{
public class Awesome
{
public void Stuff()
{
WaitUntil wait = WaitUntil.Completed;
// ...
}
}
}
C#
namespace CoolStuff.AwesomeFeature
{
using Azure;
And it compiles today. And tomorrow. But then sometime next week the preceding
(untouched) code fails with two errors:
Console
- error CS0246: The type or namespace name 'WaitUntil' could not be found
(are you missing a using directive or an assembly reference?)
- error CS0103: The name 'WaitUntil' does not exist in the current context
One of the dependencies has introduced this class in a namespace then ends with
.Azure :
C#
namespace CoolStuff.Azure
{
public class SecretsManagement
{
public string FetchFromKeyVault(string vaultId, string secretId) {
return null; }
}
}
Azure
could resolve it by adding the global:: modifier to the using declaration. However, it's
easier to place using declarations outside the namespace instead.
C#
namespace CoolStuff.AwesomeFeature
{
using global::Azure;
Style guidelines
In general, use the following format for code samples:
Comment style
Use single-line comments ( // ) for brief explanations.
Place the comment on a separate line, not at the end of a line of code.
Insert one space between the comment delimiter ( // ) and the comment text, as
shown in the following example.
C#
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.
If continuation lines aren't indented automatically, indent them one tab stop (four
spaces).
Add at least one blank line between method definitions and property definitions.
C#
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
How to display command-line
arguments
Article • 03/11/2022
executable.exe a b c "a"
"b"
"c"
"two"
"three"
7 Note
When you are running an application in Visual Studio, you can specify command-
line arguments in the Debug Page, Project Designer.
Example
This example displays the command-line arguments passed to a command-line
application. The output shown is for the first entry in the table above.
C#
See also
System.CommandLine overview
Tutorial: Get started with System.CommandLine
Explore object oriented programming
with classes and objects
Article • 04/22/2023
In this tutorial, you'll build a console application and see the basic object-oriented
features that are part of the C# language.
Prerequisites
We recommend Visual Studio for Windows. You can download a free version
from the Visual Studio downloads page . Visual Studio includes the .NET SDK.
You can also use the Visual Studio Code editor with the C# DevKit . You'll need
to install the latest .NET SDK separately.
If you prefer a different editor, you need to install the latest .NET SDK .
C#
In this tutorial, you're going to create new types that represent a bank account. Typically
developers define each class in a different text file. That makes it easier to manage as a
program grows in size. Create a new file named BankAccount.cs in the Classes directory.
This file will contain the definition of a bank account. Object Oriented programming
organizes code by creating types in the form of classes. These classes contain the code
that represents a specific entity. The BankAccount class represents a bank account. The
code implements specific operations through methods and properties. In this tutorial,
the bank account supports this behavior:
C#
namespace Classes;
Before going on, let's take a look at what you've built. The namespace declaration
provides a way to logically organize your code. This tutorial is relatively small, so you'll
put all the code in one namespace.
public class BankAccount defines the class, or type, you're creating. Everything inside
the { and } that follows the class declaration defines the state and behavior of the
class. There are five members of the BankAccount class. The first three are properties.
Properties are data elements and can have code that enforces validation or other rules.
The last two are methods. Methods are blocks of code that perform a single function.
Reading the names of each of the members should provide enough information for you
or another developer to understand what the class does.
Creating a new object of the BankAccount type means defining a constructor that
assigns those values. A constructor is a member that has the same name as the class. It's
used to initialize objects of that class type. Add the following constructor to the
BankAccount type. Place the following code above the declaration of MakeDeposit :
C#
The preceding code identifies the properties of the object being constructed by
including the this qualifier. That qualifier is usually optional and omitted. You could
also have written:
C#
The this qualifier is only required when a local variable or parameter has the same
name as that field or property. The this qualifier is omitted throughout the remainder
of this article unless it's necessary.
Constructors are called when you create an object using new. Replace the line
Console.WriteLine("Hello World!"); in Program.cs with the following code (replace
<name> with your name):
C#
using Classes;
Did you notice that the account number is blank? It's time to fix that. The account
number should be assigned when the object is constructed. But it shouldn't be the
responsibility of the caller to create it. The BankAccount class code should know how to
assign new account numbers. A simple way is to start with a 10-digit number. Increment
it when each new account is created. Finally, store the current account number when an
object is constructed.
Add a member declaration to the BankAccount class. Place the following line of code
after the opening brace { at the beginning of the BankAccount class:
C#
The accountNumberSeed is a data member. It's private , which means it can only be
accessed by code inside the BankAccount class. It's a way of separating the public
responsibilities (like having an account number) from the private implementation (how
account numbers are generated). It's also static , which means it's shared by all of the
BankAccount objects. The value of a non-static variable is unique to each instance of the
BankAccount object. The accountNumberSeed is a private static field and thus has the
number. Place them after the line that says this.Balance = initialBalance :
C#
Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;
Let's start by creating a new type to represent a transaction. The transaction is a simple
type that doesn't have any responsibilities. It needs a few properties. Create a new file
named Transaction.cs. Add the following code to it:
C#
namespace Classes;
Now, let's add a List<T> of Transaction objects to the BankAccount class. Add the
following declaration after the constructor in your BankAccount.cs file:
C#
Now, let's correctly compute the Balance . The current balance can be found by
summing the values of all transactions. As the code is currently, you can only get the
initial balance of the account, so you'll have to update the Balance property. Replace
the line public decimal Balance { get; } in BankAccount.cs with the following code:
C#
return balance;
}
}
This example shows an important aspect of properties. You're now computing the
balance when another programmer asks for the value. Your computation enumerates all
transactions, and provides the sum as the current balance.
Next, implement the MakeDeposit and MakeWithdrawal methods. These methods will
enforce the final two rules: the initial balance must be positive, and any withdrawal must
not create a negative balance.
These rules introduce the concept of exceptions. The standard way of indicating that a
method can't complete its work successfully is to throw an exception. The type of
exception and the message associated with it describe the error. Here, the MakeDeposit
method throws an exception if the amount of the deposit isn't greater than 0. The
MakeWithdrawal method throws an exception if the withdrawal amount isn't greater than
0, or if applying the withdrawal results in a negative balance. Add the following code
after the declaration of the _allTransactions list:
C#
The throw statement throws an exception. Execution of the current block ends, and
control transfers to the first matching catch block found in the call stack. You'll add a
catch block to test this code a little later on.
The constructor should get one change so that it adds an initial transaction, rather than
updating the balance directly. Since you already wrote the MakeDeposit method, call it
from your constructor. The finished constructor should look like this:
C#
Owner = name;
MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}
DateTime.Now is a property that returns the current date and time. Test this code by
adding a few deposits and withdrawals in your Main method, following the code that
creates a new BankAccount :
C#
Next, test that you're catching error conditions by trying to create an account with a
negative balance. Add the following code after the preceding code you just added:
C#
You use the try-catch statement to mark a block of code that may throw exceptions and
to catch those errors that you expect. You can use the same technique to test the code
that throws an exception for a negative balance. Add the following code before the
declaration of invalidAccount in your Main method:
C#
C#
decimal balance = 0;
report.AppendLine("Date\t\tAmount\tBalance\tNote");
foreach (var item in _allTransactions)
{
balance += item.Amount;
report.AppendLine($"
{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
}
return report.ToString();
}
The history uses the StringBuilder class to format a string that contains one line for each
transaction. You've seen the string formatting code earlier in these tutorials. One new
character is \t . That inserts a tab to format the output.
C#
Console.WriteLine(account.GetAccountHistory());
Next steps
If you got stuck, you can see the source for this tutorial in our GitHub repo .
Selection statements
Iteration statements
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Object-Oriented programming (C#)
Article • 07/11/2023
In the preceding tutorial, introduction to classes you saw both abstraction and
encapsulation. The BankAccount class provided an abstraction for the concept of a bank
account. You could modify its implementation without affecting any of the code that
used the BankAccount class. Both the BankAccount and Transaction classes provide
encapsulation of the components needed to describe those concepts in code.
In this tutorial, you'll extend that application to make use of inheritance and
polymorphism to add new features. You'll also add features to the BankAccount class,
taking advantage of the abstraction and encapsulation techniques you learned in the
preceding tutorial.
An interest earning account that accrues interest at the end of each month.
A line of credit that can have a negative balance, but when there's a balance,
there's an interest charge each month.
A pre-paid gift card account that starts with a single deposit, and only can be paid
off. It can be refilled once at the start of each month.
All of these different accounts are similar to BankAccount class defined in the earlier
tutorial. You could copy that code, rename the classes, and make modifications. That
technique would work in the short term, but it would be more work over time. Any
changes would be copied across all the affected classes.
Instead, you can create new bank account types that inherit methods and data from the
BankAccount class created in the preceding tutorial. These new classes can extend the
BankAccount class with the specific behavior needed for each type:
C#
Each of these classes inherits the shared behavior from their shared base class, the
BankAccount class. Write the implementations for new and different functionality in each
of the derived classes. These derived classes already have all the behavior defined in the
BankAccount class.
It's a good practice to create each new class in a different source file. In Visual Studio ,
you can right-click on the project, and select add class to add a new class in a new file. In
Visual Studio Code , select File then New to create a new source file. In either tool,
name the file to match the class: InterestEarningAccount.cs, LineOfCreditAccount.cs, and
GiftCardAccount.cs.
When you create the classes as shown in the preceding sample, you'll find that none of
your derived classes compile. A constructor is responsible for initializing an object. A
derived class constructor must initialize the derived class, and provide instructions on
how to initialize the base class object included in the derived class. The proper
initialization normally happens without any extra code. The BankAccount class declares
one public constructor with the following signature:
C#
The compiler doesn't generate a default constructor when you define a constructor
yourself. That means each derived class must explicitly call this constructor. You declare
a constructor that can pass arguments to the base class constructor. The following code
shows the constructor for the InterestEarningAccount :
C#
The parameters to this new constructor match the parameter type and names of the
base class constructor. You use the : base() syntax to indicate a call to a base class
constructor. Some classes define multiple constructors, and this syntax enables you to
pick which base class constructor you call. Once you've updated the constructors, you
can develop the code for each of the derived classes. The requirements for the new
classes can be stated as follows:
You can see that all three of these account types have an action that takes places at the
end of each month. However, each account type does different tasks. You use
polymorphism to implement this code. Create a single virtual method in the
BankAccount class:
C#
The preceding code shows how you use the virtual keyword to declare a method in
the base class that a derived class may provide a different implementation for. A
virtual method is a method where any derived class may choose to reimplement. The
derived classes use the override keyword to define the new implementation. Typically
you refer to this as "overriding the base class implementation". The virtual keyword
specifies that derived classes may override the behavior. You can also declare abstract
methods where derived classes must override the behavior. The base class does not
provide an implementation for an abstract method. Next, you need to define the
implementation for two of the new classes you've created. Start with the
InterestEarningAccount :
C#
Add the following code to the LineOfCreditAccount . The code negates the balance to
compute a positive interest charge that is withdrawn from the account:
C#
C#
The constructor provides a default value for the monthlyDeposit value so callers can
omit a 0 for no monthly deposit. Next, override the PerformMonthEndTransactions
method to add the monthly deposit, if it was set to a non-zero value in the constructor:
C#
The override applies the monthly deposit set in the constructor. Add the following code
to the Main method to test these changes for the GiftCardAccount and the
InterestEarningAccount :
C#
Verify the results. Now, add a similar set of test code for the LineOfCreditAccount :
C#
Console
7 Note
The actual output includes the full path to the folder with the project. The folder
names were omitted for brevity. Also, depending on your code format, the line
numbers may be slightly different.
This code fails because the BankAccount assumes that the initial balance must be greater
than 0. Another assumption baked into the BankAccount class is that the balance can't
go negative. Instead, any withdrawal that overdraws the account is rejected. Both of
those assumptions need to change. The line of credit account starts at 0, and generally
will have a negative balance. Also, if a customer borrows too much money, they incur a
fee. The transaction is accepted, it just costs more. The first rule can be implemented by
adding an optional argument to the BankAccount constructor that specifies the
minimum balance. The default is 0 . The second rule requires a mechanism that enables
derived classes to modify the default algorithm. In a sense, the base class "asks" the
derived type what should happen when there's an overdraft. The default behavior is to
reject the transaction by throwing an exception.
C#
private readonly decimal _minimumBalance;
Owner = name;
_minimumBalance = minimumBalance;
if (initialBalance > 0)
MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}
The preceding code shows two new techniques. First, the minimumBalance field is marked
as readonly . That means the value cannot be changed after the object is constructed.
Once a BankAccount is created, the minimumBalance can't change. Second, the
constructor that takes two parameters uses : this(name, initialBalance, 0) { } as its
implementation. The : this() expression calls the other constructor, the one with three
parameters. This technique allows you to have a single implementation for initializing an
object even though client code can choose one of many constructors.
This implementation calls MakeDeposit only if the initial balance is greater than 0 . That
preserves the rule that deposits must be positive, yet lets the credit account open with a
0 balance.
Now that the BankAccount class has a read-only field for the minimum balance, the final
change is to change the hard code 0 to minimumBalance in the MakeWithdrawal method:
C#
After extending the BankAccount class, you can modify the LineOfCreditAccount
constructor to call the new base constructor, as shown in the following code:
C#
One technique is to define a virtual function where you implement the required
behavior. The BankAccount class refactors the MakeWithdrawal method into two
methods. The new method does the specified action when the withdrawal takes the
balance below the minimum. The existing MakeWithdrawal method has the following
code:
C#
C#
The added method is protected , which means that it can be called only from derived
classes. That declaration prevents other clients from calling the method. It's also
virtual so that derived classes can change the behavior. The return type is a
Transaction? . The ? annotation indicates that the method may return null . Add the
C#
The override returns a fee transaction when the account is overdrawn. If the withdrawal
doesn't go over the limit, the method returns a null transaction. That indicates there's
no fee. Test these changes by adding the following code to your Main method in the
Program class:
C#
Summary
If you got stuck, you can see the source for this tutorial in our GitHub repo .
You used Abstraction when you defined classes for each of the different account
types. Those classes described the behavior for that type of account.
You used Encapsulation when you kept many details private in each class.
You used Inheritance when you leveraged the implementation already created in
the BankAccount class to save code.
You used Polymorphism when you created virtual methods that derived classes
could override to create specific behavior for that account type.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Inheritance in C# and .NET
Article • 02/03/2023
Prerequisites
We recommend Visual Studio for Windows. You can download a free version
from the Visual Studio downloads page . Visual Studio includes the .NET SDK.
You can also use the Visual Studio Code editor with the C# DevKit . You'll need
to install the latest .NET SDK separately.
If you prefer a different editor, you need to install the latest .NET SDK .
2. Enter the dotnet new console command at a command prompt to create a new
.NET Core project.
3. Copy and paste the code from the example into your code editor.
4. Enter the dotnet restore command from the command line to load or restore the
project's dependencies.
You don't have to run dotnet restore because it's run implicitly by all commands
that require a restore to occur, such as dotnet new , dotnet build , dotnet run ,
dotnet test , dotnet publish , and dotnet pack . To disable implicit restore, use the
--no-restore option.
The dotnet restore command is still useful in certain scenarios where explicitly
restoring makes sense, such as continuous integration builds in Azure DevOps
Services or in build systems that need to explicitly control when the restore occurs.
For information about how to manage NuGet feeds, see the dotnet restore
documentation.
5. Enter the dotnet run command to compile and execute the example.
C# and .NET support single inheritance only. That is, a class can only inherit from a single
class. However, inheritance is transitive, which allows you to define an inheritance
hierarchy for a set of types. In other words, type D can inherit from type C , which
inherits from type B , which inherits from the base class type A . Because inheritance is
transitive, the members of type A are available to type D .
Not all members of a base class are inherited by derived classes. The following members
are not inherited:
Instance constructors, which you call to create a new instance of the class. Each
class must define its own constructors.
Finalizers, which are called by the runtime's garbage collector to destroy instances
of a class.
While all other members of a base class are inherited by derived classes, whether they
are visible or not depends on their accessibility. A member's accessibility affects its
visibility for derived classes as follows:
Private members are visible only in derived classes that are nested in their base
class. Otherwise, they are not visible in derived classes. In the following example,
A.B is a nested class that derives from A , and C derives from A . The private
A._value field is visible in A.B. However, if you remove the comments from the
C#
public class A
{
private int _value = 10;
public class B : A
{
public int GetValue()
{
return _value;
}
}
}
public class C : A
{
// public int GetValue()
// {
// return _value;
// }
}
Internal members are visible only in derived classes that are located in the same
assembly as the base class. They are not visible in derived classes located in a
different assembly from the base class.
Public members are visible in derived classes and are part of the derived class'
public interface. Public inherited members can be called just as if they are defined
in the derived class. In the following example, class A defines a method named
Method1 , and class B inherits from class A . The example then calls Method1 as if it
C#
public class A
{
public void Method1()
{
// Method implementation.
}
}
public class B : A
{ }
C#
public class A
{
public void Method1()
{
// Do something.
}
}
public class B : A
{
public override void Method1() // Generates CS0506.
{
// Do something else.
}
}
In some cases, a derived class must override the base class implementation. Base class
members marked with the abstract keyword require that derived classes override them.
Attempting to compile the following example generates compiler error CS0534, "
<class> does not implement inherited abstract member <member>", because class B
provides no implementation for A.Method1 .
C#
public abstract class A
{
public abstract void Method1();
}
Inheritance applies only to classes and interfaces. Other type categories (structs,
delegates, and enums) do not support inheritance. Because of these rules, attempting to
compile code like the following example produces compiler error CS0527: "Type
'ValueType' in interface list is not an interface." The error message indicates that,
although you can define the interfaces that a struct implements, inheritance is not
supported.
C#
Implicit inheritance
Besides any types that they may inherit from through single inheritance, all types in the
.NET type system implicitly inherit from Object or a type derived from it. The common
functionality of Object is available to any type.
To see what implicit inheritance means, let's define a new class, SimpleClass , that is
simply an empty class definition:
C#
You can then use reflection (which lets you inspect a type's metadata to get information
about that type) to get a list of the members that belong to the SimpleClass type.
Although you haven't defined any members in your SimpleClass class, output from the
example indicates that it actually has nine members. One of these members is a
parameterless (or default) constructor that is automatically supplied for the SimpleClass
type by the C# compiler. The remaining eight are members of Object, the type from
which all classes and interfaces in the .NET type system ultimately implicitly inherit.
C#
using System.Reflection;
Implicit inheritance from the Object class makes these methods available to the
SimpleClass class:
The public ToString method, which converts a SimpleClass object to its string
representation, returns the fully qualified type name. In this case, the ToString
method returns the string "SimpleClass".
Three methods that test for equality of two objects: the public instance
Equals(Object) method, the public static Equals(Object, Object) method, and the
The public GetHashCode method, which computes a value that allows an instance of
the type to be used in hashed collections.
The public GetType method, which returns a Type object that represents the
SimpleClass type.
Because of implicit inheritance, you can call any inherited member from a SimpleClass
object just as if it was actually a member defined in the SimpleClass class. For instance,
the following example calls the SimpleClass.ToString method, which SimpleClass
inherits from Object.
C#
The following table lists the categories of types that you can create in C# and the types
from which they implicitly inherit. Each base type makes a different set of members
available through inheritance to implicitly derived types.
ノ Expand table
class Object
7 Note
Note that "is a" also expresses the relationship between a type and a specific
instantiation of that type. In the following example, Automobile is a class that has three
unique read-only properties: Make , the manufacturer of the automobile; Model , the kind
of automobile; and Year , its year of manufacture. Your Automobile class also has a
constructor whose arguments are assigned to the property values, and it overrides the
Object.ToString method to produce a string that uniquely identifies the Automobile
instance rather than the Automobile class.
C#
if (model == null)
throw new ArgumentNullException(nameof(model), "The model cannot
be null.");
else if (string.IsNullOrWhiteSpace(model))
throw new ArgumentException("model cannot be an empty string or
have space characters only.");
Model = model;
In this case, you shouldn't rely on inheritance to represent specific car makes and
models. For example, you don't need to define a Packard type to represent automobiles
manufactured by the Packard Motor Car Company. Instead, you can represent them by
creating an Automobile object with the appropriate values passed to its class
constructor, as the following example does.
C#
using System;
An is-a relationship based on inheritance is best applied to a base class and to derived
classes that add additional members to the base class or that require additional
functionality not present in the base class.
What members to include in your base Publication class, and whether the
Publication members provide method implementations or whether Publication
is an abstract base class that serves as a template for its derived classes.
In this case, the Publication class will provide method implementations. The
Designing abstract base classes and their derived classes section contains an
example that uses an abstract base class to define the methods that derived
classes must override. Derived classes are free to provide any implementation that
is suitable for the derived type.
The ability to reuse code (that is, multiple derived classes share the declaration and
implementation of base class methods and do not need to override them) is an
advantage of non-abstract base classes. Therefore, you should add members to
Publication if their code is likely to be shared by some or most specialized
Publication types. If you fail to provide base class implementations efficiently,
Both to maximize code reuse and to create a logical and intuitive inheritance
hierarchy, you want to be sure that you include in the Publication class only the
data and functionality that is common to all or to most publications. Derived
classes then implement members that are unique to the particular kinds of
publication that they represent.
How far to extend your class hierarchy. Do you want to develop a hierarchy of
three or more classes, rather than simply a base class and one or more derived
classes? For example, Publication could be a base class of Periodical , which in
turn is a base class of Magazine , Journal and Newspaper .
For your example, you'll use the small hierarchy of a Publication class and a single
derived class, Book . You could easily extend the example to create a number of
additional classes that derive from Publication , such as Magazine and Article .
Whether it makes sense to instantiate the base class. If it does not, you should
apply the abstract keyword to the class. Otherwise, your Publication class can be
instantiated by calling its class constructor. If an attempt is made to instantiate a
class marked with the abstract keyword by a direct call to its class constructor, the
C# compiler generates error CS0144, "Cannot create an instance of the abstract
class or interface." If an attempt is made to instantiate the class by using reflection,
the reflection method throws a MemberAccessException.
By default, a base class can be instantiated by calling its class constructor. You do
not have to explicitly define a class constructor. If one is not present in the base
class' source code, the C# compiler automatically provides a default
(parameterless) constructor.
For your example, you'll mark the Publication class as abstract so that it cannot
be instantiated. An abstract class without any abstract methods indicates that
this class represents an abstract concept that is shared among several concrete
classes (like a Book , Journal ).
Whether derived classes must inherit the base class implementation of particular
members, whether they have the option to override the base class implementation,
or whether they must provide an implementation. You use the abstract keyword to
force derived classes to provide an implementation. You use the virtual keyword to
allow derived classes to override a base class method. By default, methods defined
in the base class are not overridable.
The Publication class does not have any abstract methods, but the class itself is
abstract .
Whether a derived class represents the final class in the inheritance hierarchy and
cannot itself be used as a base class for additional derived classes. By default, any
class can serve as a base class. You can apply the sealed keyword to indicate that a
class cannot serve as a base class for any additional classes. Attempting to derive
from a sealed class generated compiler error CS0509, "cannot derive from sealed
type <typeName>."
The following example shows the source code for the Publication class, as well as a
PublicationType enumeration that is returned by the Publication.PublicationType
property. In addition to the members that it inherits from Object, the Publication class
defines the following unique members and member overrides:
C#
if (string.IsNullOrWhiteSpace(title))
throw new ArgumentException("The title is required.");
Title = title;
Type = type;
}
A constructor
However, its instance constructor can be called directly from derived class
constructors, as the source code for the Book class shows.
Publication constructor.
Pages is a read-write Int32 property that indicates how many total pages the
publication has. The value is stored in a private field named totalPages . It must be
a positive number or an ArgumentOutOfRangeException is thrown.
Publisher-related members
Two read-only properties, Publisher and Type . The values are originally supplied
by the call to the Publication class constructor.
Publishing-related members
Two methods, Publish and GetPublicationDate , set and return the publication
date. The Publish method sets a private published flag to true when it is called
and assigns the date passed to it as an argument to the private datePublished
field. The GetPublicationDate method returns the string "NYP" if the published
flag is false , and the value of the datePublished field if it is true .
Copyright-related members
The Copyright method takes the name of the copyright holder and the year of the
copyright as arguments and assigns them to the CopyrightName and CopyrightDate
properties.
If a type does not override the Object.ToString method, it returns the fully qualified
name of the type, which is of little use in differentiating one instance from another.
The Publication class overrides Object.ToString to return the value of the Title
property.
The following figure illustrates the relationship between your base Publication class
and its implicitly inherited Object class.
C#
using System;
Author = author;
}
if (currency.Length != 3)
throw new ArgumentException("The ISO currency symbol is a 3-
character string.");
Currency = currency;
return oldValue;
}
In addition to the members that it inherits from Publication , the Book class defines the
following unique members and member overrides:
Two constructors
The two Book constructors share three common parameters. Two, title and
publisher, correspond to parameters of the Publication constructor. The third is
author, which is stored to a public immutable Author property. One constructor
includes an isbn parameter, which is stored in the ISBN auto-property.
The first constructor uses the this keyword to call the other constructor.
Constructor chaining is a common pattern in defining constructors. Constructors
with fewer parameters provide default values when calling the constructor with the
greatest number of parameters.
The second constructor uses the base keyword to pass the title and publisher
name to the base class constructor. If you don't make an explicit call to a base class
constructor in your source code, the C# compiler automatically supplies a call to
the base class' default or parameterless constructor.
A read-only ISBN property, which returns the Book object's International Standard
Book Number, a unique 10- or 13-digit number. The ISBN is supplied as an
argument to one of the Book constructors. The ISBN is stored in a private backing
field, which is auto-generated by the compiler.
Two read-only price-related properties, Price and Currency . Their values are
provided as arguments in a SetPrice method call. The Currency property is the
three-digit ISO currency symbol (for example, USD for the U.S. dollar). ISO currency
symbols can be retrieved from the ISOCurrencySymbol property. Both of these
properties are externally read-only, but both can be set by code in the Book class.
A SetPrice method, which sets the values of the Price and Currency properties.
Those values are returned by those same properties.
When you override the Object.Equals(Object) method, you must also override the
GetHashCode method, which returns a value that the runtime uses to store items
in hashed collections for efficient retrieval. The hash code should return a value
that's consistent with the test for equality. Since you've overridden
Object.Equals(Object) to return true if the ISBN properties of two Book objects are
equal, you return the hash code computed by calling the GetHashCode method of
the string returned by the ISBN property.
The following figure illustrates the relationship between the Book class and Publication ,
its base class.
You can now instantiate a Book object, invoke both its unique and inherited members,
and pass it as an argument to a method that expects a parameter of type Publication
or of type Book , as the following example shows.
C#
For example, each closed two-dimensional geometric shape includes two properties:
area, the inner extent of the shape; and perimeter, or the distance along the edges of
the shape. The way in which these properties are calculated, however, depends
completely on the specific shape. The formula for calculating the perimeter (or
circumference) of a circle, for example, is different from that of a square. The Shape class
is an abstract class with abstract methods. That indicates derived classes share the
same functionality, but those derived classes implement that functionality differently.
The following example defines an abstract base class named Shape that defines two
properties: Area and Perimeter . In addition to marking the class with the abstract
keyword, each instance member is also marked with the abstract keyword. In this case,
Shape also overrides the Object.ToString method to return the name of the type, rather
than its fully qualified name. And it defines two static members, GetArea and
GetPerimeter , that allow callers to easily retrieve the area and perimeter of an instance
of any derived class. When you pass an instance of a derived class to either of these
methods, the runtime calls the method override of the derived class.
C#
You can then derive some classes from Shape that represent specific shapes. The
following example defines three classes, Square , Rectangle , and Circle . Each uses a
formula unique for that particular shape to compute the area and perimeter. Some of
the derived classes also define properties, such as Rectangle.Diagonal and
Circle.Diameter , that are unique to the shape that they represent.
C#
using System;
The following example uses objects derived from Shape . It instantiates an array of
objects derived from Shape and calls the static methods of the Shape class, which wraps
return Shape property values. The runtime retrieves values from the overridden
properties of the derived types. The example also casts each Shape object in the array to
its derived type and, if the cast succeeds, retrieves properties of that particular subclass
of Shape .
C#
using System;
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
How to safely cast by using pattern
matching and the is and as operators
Article • 03/11/2022
Because objects are polymorphic, it's possible for a variable of a base class type to hold
a derived type. To access the derived type's instance members, it's necessary to cast the
value back to the derived type. However, a cast creates the risk of throwing an
InvalidCastException. C# provides pattern matching statements that perform a cast
conditionally only when it will succeed. C# also provides the is and as operators to test if
a value is of a certain type.
The following example shows how to use the pattern matching is statement:
C#
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 { }
The preceding sample demonstrates a few features of pattern matching syntax. The if
(a is Mammal m) statement combines the test with an initialization assignment. The
assignment occurs only when the test succeeds. The variable m is only in scope in the
embedded if statement where it has been assigned. You can't access m later in the
same method. The preceding example also shows how to use the as operator to convert
an object to a specified type.
You can also use the same syntax for testing if a nullable value type has a value, as
shown in the following example:
C#
int i = 5;
PatternMatchingNullable(i);
int? j = null;
PatternMatchingNullable(j);
double d = 9.78654;
PatternMatchingNullable(d);
PatternMatchingSwitch(i);
PatternMatchingSwitch(j);
PatternMatchingSwitch(d);
The preceding sample demonstrates other features of pattern matching to use with
conversions. You can test a variable for the null pattern by checking specifically for the
null value. When the runtime value of the variable is null , an is statement checking
for a type always returns false . The pattern matching is statement doesn't allow a
nullable value type, such as int? or Nullable<int> , but you can test for any other value
type. The is patterns from the preceding example aren't limited to the nullable value
types. You can also use those patterns to test if a variable of a reference type has a value
or it's null .
The preceding sample also shows how you use the type pattern in a switch statement
where the variable may be one of many different types.
If you want to test if a variable is a given type, but not assign it to a new variable, you
can use the is and as operators for reference types and nullable value types. The
following code shows how to use the is and as statements that were part of the C#
language before pattern matching was introduced to test if a variable is of a given type:
C#
double d = 9.78654;
UseAsWithNullable(d);
class SuperNova { }
As you can see by comparing this code with the pattern matching code, the pattern
matching syntax provides more robust features by combining the test and the
assignment in a single statement. Use the pattern matching syntax whenever possible.
Tutorial: Use pattern matching to build
type-driven and data-driven algorithms
Article • 02/15/2023
You can write functionality that behaves as though you extended types that may be in
other libraries. Another use for patterns is to create functionality your application
requires that isn't a fundamental feature of the type being extended.
Prerequisites
We recommend Visual Studio for Windows or Mac. You can download a free
version from the Visual Studio downloads page . Visual Studio includes the .NET
SDK.
You can also use the Visual Studio Code editor. You'll need to install the latest
.NET SDK separately.
If you prefer a different editor, you need to install the latest .NET SDK .
This tutorial assumes you're familiar with C# and .NET, including either Visual Studio or
the .NET CLI.
The classic object-oriented design would call for creating types in your application that
represent each data type from those multiple data sources. Then, your application
would work with those new types, build inheritance hierarchies, create virtual methods,
and implement abstractions. Those techniques work, and sometimes they're the best
tools. Other times you can write less code. You can write more clear code using
techniques that separate the data from the operations that manipulate that data.
In this tutorial, you'll create and explore an application that takes incoming data from
several external sources for a single scenario. You'll see how pattern matching provides
an efficient way to consume and process that data in ways that weren't part of the
original system.
Consider a major metropolitan area that is using tolls and peak time pricing to manage
traffic. You write an application that calculates tolls for a vehicle based on its type. Later
enhancements incorporate pricing based on the number of occupants in the vehicle.
Further enhancements add pricing based on the time and the day of the week.
From that brief description, you may have quickly sketched out an object hierarchy to
model this system. However, your data is coming from multiple sources like other
vehicle registration management systems. These systems provide different classes to
model that data and you don't have a single object model you can use. In this tutorial,
you'll use these simplified classes to model for the vehicle data from these external
systems, as shown in the following code:
C#
namespace ConsumerVehicleRegistration
{
public class Car
{
public int Passengers { get; set; }
}
}
namespace CommercialRegistration
{
public class DeliveryTruck
{
public int GrossWeightClass { get; set; }
}
}
namespace LiveryRegistration
{
public class Taxi
{
public int Fares { get; set; }
}
The objects you need to work with aren't in an object hierarchy that matches your
goals. You may be working with classes that are part of unrelated systems.
The functionality you're adding isn't part of the core abstraction for these classes.
The toll paid by a vehicle changes for different types of vehicles, but the toll isn't a
core function of the vehicle.
When the shape of the data and the operations on that data aren't described together,
the pattern matching features in C# make it easier to work with.
A Car is $2.00.
A Taxi is $3.50.
A Bus is $5.00.
A DeliveryTruck is $10.00
Create a new TollCalculator class, and implement pattern matching on the vehicle type
to get the toll amount. The following code shows the initial implementation of the
TollCalculator .
C#
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;
namespace Calculators;
The preceding code uses a switch expression (not the same as a switch statement) that
tests the declaration pattern. A switch expression begins with the variable, vehicle in
the preceding code, followed by the switch keyword. Next comes all the switch arms
inside curly braces. The switch expression makes other refinements to the syntax that
surrounds the switch statement. The case keyword is omitted, and the result of each
arm is an expression. The last two arms show a new language feature. The { } case
matches any non-null object that didn't match an earlier arm. This arm catches any
incorrect types passed to this method. The { } case must follow the cases for each
vehicle type. If the order were reversed, the { } case would take precedence. Finally, the
null constant pattern detects when null is passed to this method. The null pattern
can be last because the other patterns match only a non-null object of the correct type.
You can test this code using the following code in Program.cs :
C#
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;
using toll_calculator;
try
{
tollCalc.CalculateToll("this will fail");
}
catch (ArgumentException e)
{
Console.WriteLine("Caught an argument exception when using the wrong
type");
}
try
{
tollCalc.CalculateToll(null!);
}
catch (ArgumentNullException e)
{
Console.WriteLine("Caught an argument exception when using null");
}
That code is included in the starter project, but is commented out. Remove the
comments, and you can test what you've written.
You're starting to see how patterns can help you create algorithms where the code and
the data are separate. The switch expression tests the type and produces different
values based on the results. That's only the beginning.
These rules can be implemented using a property pattern in the same switch expression.
A property pattern compares a property value to a constant value. The property pattern
examines properties of the object once the type has been determined. The single case
for a Car expands to four different cases:
C#
vehicle switch
{
Car {Passengers: 0} => 2.00m + 0.50m,
Car {Passengers: 1} => 2.0m,
Car {Passengers: 2} => 2.0m - 0.50m,
Car => 2.00m - 1.0m,
// ...
};
The first three cases test the type as a Car , then check the value of the Passengers
property. If both match, that expression is evaluated and returned.
You would also expand the cases for taxis in a similar manner:
C#
vehicle switch
{
// ...
// ...
};
Next, implement the occupancy rules by expanding the cases for buses, as shown in the
following example:
C#
vehicle switch
{
// ...
// ...
};
The toll authority isn't concerned with the number of passengers in the delivery trucks.
Instead, they adjust the toll amount based on the weight class of the trucks as follows:
C#
vehicle switch
{
// ...
The preceding code shows the when clause of a switch arm. You use the when clause to
test conditions other than equality on a property. When you've finished, you'll have a
method that looks much like the following code:
C#
vehicle switch
{
Car {Passengers: 0} => 2.00m + 0.50m,
Car {Passengers: 1} => 2.0m,
Car {Passengers: 2} => 2.0m - 0.50m,
Car => 2.00m - 1.0m,
Many of these switch arms are examples of recursive patterns. For example, Car {
Passengers: 1} shows a constant pattern inside a property pattern.
You can make this code less repetitive by using nested switches. The Car and Taxi both
have four different arms in the preceding examples. In both cases, you can create a
declaration pattern that feeds into a constant pattern. This technique is shown in the
following code:
C#
In the preceding sample, using a recursive expression means you don't repeat the Car
and Taxi arms containing child arms that test the property value. This technique isn't
used for the Bus and DeliveryTruck arms because those arms are testing ranges for the
property, not discrete values.
C#
The preceding code does work correctly, but isn't readable. You have to chain through
all the input cases and the nested if statements to reason about the code. Instead,
you'll use pattern matching for this feature, but you'll integrate it with other techniques.
You could build a single pattern match expression that would account for all the
combinations of direction, day of the week, and time. The result would be a complicated
expression. It would be hard to read and difficult to understand. That makes it hard to
ensure correctness. Instead, combine those methods to build a tuple of values that
concisely describes all those states. Then use pattern matching to calculate a multiplier
for the toll. The tuple contains three discrete conditions:
The following table shows the combinations of input values and the peak pricing
multiplier:
ノ Expand table
There are 16 different combinations of the three variables. By combining some of the
conditions, you'll simplify the final switch expression.
The system that collects the tolls uses a DateTime structure for the time when the toll
was collected. Build member methods that create the variables from the preceding
table. The following function uses a pattern matching switch expression to express
whether a DateTime represents a weekend or a weekday:
C#
That method is correct, but it's repetitious. You can simplify it, as shown in the following
code:
C#
Next, add a similar function to categorize the time into the blocks:
C#
You add a private enum to convert each range of time to a discrete value. Then, the
GetTimeBand method uses relational patterns, and conjunctive or patterns. A relational
pattern lets you test a numeric value using < , > , <= , or >= . The or pattern tests if an
expression matches one or more patterns. You can also use an and pattern to ensure
that an expression matches two distinct patterns, and a not pattern to test that an
expression doesn't match a pattern.
After you create those methods, you can use another switch expression with the tuple
pattern to calculate the pricing premium. You could build a switch expression with all
16 arms:
C#
The above code works, but it can be simplified. All eight combinations for the weekend
have the same toll. You can replace all eight with the following line:
C#
Both inbound and outbound traffic have the same multiplier during the weekday
daytime and overnight hours. Those four switch arms can be replaced with the following
two lines:
C#
The code should look like the following code after those two changes:
C#
Finally, you can remove the two rush hour times that pay the regular price. Once you
remove those arms, you can replace the false with a discard ( _ ) in the final switch arm.
You'll have the following finished method:
C#
This example highlights one of the advantages of pattern matching: the pattern
branches are evaluated in order. If you rearrange them so that an earlier branch handles
one of your later cases, the compiler warns you about the unreachable code. Those
language rules made it easier to do the preceding simplifications with confidence that
the code didn't change.
Pattern matching makes some types of code more readable and offers an alternative to
object-oriented techniques when you can't add code to your classes. The cloud is
causing data and functionality to live apart. The shape of the data and the operations on
it aren't necessarily described together. In this tutorial, you consumed existing data in
entirely different ways from its original function. Pattern matching gave you the ability
to write functionality that overrode those types, even though you couldn't extend them.
Next steps
You can download the finished code from the dotnet/samples GitHub repository.
Explore patterns on your own and add this technique into your regular coding activities.
Learning these techniques gives you another way to approach problems and create new
functionality.
See also
Patterns
switch expression
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
How to handle an exception using
try/catch
Article • 04/22/2023
Example
In this example, IndexOutOfRangeException isn't the most appropriate exception:
ArgumentOutOfRangeException makes more sense for the method because the error is
caused by the index argument passed in by the caller.
C#
Comments
The code that causes an exception is enclosed in the try block. A catch statement is
added immediately after it to handle IndexOutOfRangeException , if it occurs. The catch
block handles the IndexOutOfRangeException and throws the more appropriate
ArgumentOutOfRangeException instead. In order to provide the caller with as much
The purpose of a finally statement is to ensure that the necessary cleanup of objects,
usually objects that are holding external resources, occurs immediately, even if an
exception is thrown. One example of such cleanup is calling Close on a FileStream
immediately after use instead of waiting for the object to be garbage collected by the
common language runtime, as follows:
C#
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
file.Close();
}
Example
To turn the previous code into a try-catch-finally statement, the cleanup code is
separated from the working code, as follows.
C#
try
{
fileInfo = new FileInfo("./file.txt");
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
}
finally
{
file?.Close();
}
}
Because an exception can occur at any time within the try block before the
OpenWrite() call, or the OpenWrite() call itself could fail, we aren't guaranteed that the
file is open when we try to close it. The finally block adds a check to make sure that
the FileStream object isn't null before you call the Close method. Without the null
check, the finally block could throw its own NullReferenceException, but throwing
exceptions in finally blocks should be avoided if it's possible.
A database connection is another good candidate for being closed in a finally block.
Because the number of connections allowed to a database server is sometimes limited,
you should close database connections as quickly as possible. If an exception is thrown
before you can close your connection, using the finally block is better than waiting for
garbage collection.
See also
using statement
Exception-handling statements
What's new in C# 13
Article • 07/30/2024
C# 13 includes the following new features. You can try these features using the latest
Visual Studio 2022 version or the .NET 9 Preview SDK .
params collections
New lock type and semantics.
New escape sequence - \e.
Method group natural type improvements
Implicit indexer access in object initializers
Enable ref locals and unsafe contexts in iterators and async methods
Enable ref struct types to implement interfaces.
Allow ref struct types as arguments for type parameters in generics.
You can download the latest .NET 9 preview SDK from the .NET downloads page . You
can also download Visual Studio 2022 - preview , which includes the .NET 9 Preview
SDK.
New features are added to the "What's new in C#" page when they're available in public
preview releases. The working set section of the roslyn feature status page tracks
when upcoming features are merged into the main branch.
You can find any breaking changes introduced in C# 13 in our article on breaking
changes.
7 Note
We're interested in your feedback on these features. If you find issues with any of
these new features, create a new issue in the dotnet/roslyn repository.
params collections
The params modifier isn't limited to array types. You can now use params with any
recognized collection type, including System.Span<T>, System.ReadOnlySpan<T>, and
types that implement System.Collections.Generic.IEnumerable<T> and have an Add
method. In addition to concrete types, the interfaces
System.Collections.Generic.IEnumerable<T>,
System.Collections.Generic.IReadOnlyCollection<T>,
System.Collections.Generic.IReadOnlyList<T>,
System.Collections.Generic.ICollection<T>, and System.Collections.Generic.IList<T> can
also be used.
When an interface type is used, the compiler synthesizes the storage for the arguments
supplied. You can learn more in the feature specification for params collections.
The C# lock statement recognizes if the target of the lock is a Lock object. If so, it uses
the updated API, rather than the traditional API using System.Threading.Monitor. The
compiler also recognizes if you convert a Lock object to another type and the Monitor
based code would be generated. You can read more in the feature specification for the
new lock object.
if the next characters following 1b were valid hexadecimal digits, those characters
became part of the escape sequence.
The new behavior is to prune the set of candidate methods at each scope, removing
those candidate methods that aren't applicable. Typically, the removed methods are
generic methods with the wrong arity, or constraints that aren't satisfied. The process
continues to the next outer scope only if no candidate methods are found. This process
more closely follows the general algorithm for overload resolution. If all candidate
methods found at a given scope don't match, the method group doesn't have a natural
type.
You can read the details of the changes in the proposal specification.
C#
The preceding example creates an array that counts down from 9 to 0. In versions
before C# 13, the ^ operator can't be used in an object initializer. You need to index the
elements from the front.
In C# 13, async methods can declare ref local variables, or local variables of a ref
struct type. However, those variables can't be accessed across an await boundary.
Neither can they be accessed across a yield return boundary.
This relaxed restriction enables the compiler to allow verifiably safe use of ref local
variables and ref struct types in more places. You can safely use types like
System.ReadOnlySpan<T> in these methods. The compiler tells you if you violate safety
rules.
In the same fashion, C# 13 allows unsafe contexts in iterator methods. However, all
yield return and yield break statements must be in safe contexts.
that type parameter can be a ref struct type. The compiler enforces ref safety rules on
all instances of that type parameter.
See also
What's new in .NET 9
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
Provide product feedback
more information, see our
contributor guide.
What's new in C# 12
Article • 06/04/2024
C# 12 includes the following new features. You can try these features using the latest
Visual Studio 2022 version or the .NET 8 SDK .
ref readonly parameters - Introduced in Visual Studio 2022 version 17.8 Preview 2.
Alias any type - Introduced in Visual Studio 2022 version 17.6 Preview 3.
You can download the latest .NET 8 SDK from the .NET downloads page . You can also
download Visual Studio 2022 , which includes the .NET 8 SDK.
7 Note
We're interested in your feedback on these features. If you find issues with any of
these new features, create a new issue in the dotnet/roslyn repository.
Primary constructors
You can now create primary constructors in any class and struct . Primary constructors
are no longer restricted to record types. Primary constructor parameters are in scope
for the entire body of the class. To ensure that all primary constructor parameters are
definitely assigned, all explicitly declared constructors must call the primary constructor
using this() syntax. Adding a primary constructor to a class prevents the compiler
from declaring an implicit parameterless constructor. In a struct , the implicit
parameterless constructor initializes all fields, including primary constructor parameters
to the 0-bit pattern.
The compiler generates public properties for primary constructor parameters only in
record types, either record class or record struct types. Nonrecord classes and
structs might not always want this behavior for primary constructor parameters.
You can learn more about primary constructors in the tutorial for exploring primary
constructors and in the article on instance constructors.
Collection expressions
Collection expressions introduce a new terse syntax to create common collection values.
Inlining other collections into these values is possible using a spread element ..e .
Several collection-like types can be created without requiring external BCL support.
These types are:
C#
// Create an array:
int[] a = [1, 2, 3, 4, 5, 6, 7, 8];
// Create a list:
List<string> b = ["one", "two", "three"];
// Create a span
Span<char> c = ['a', 'b', 'c', 'd', 'e', 'f', 'h', 'i'];
C#
The spread element evaluates each element of the enumerations expression. Each
element is included in the output collection.
You can use collection expressions anywhere you need a collection of elements. They
can specify the initial value for a collection or be passed as arguments to methods that
take collection types. You can learn more about collection expressions in the language
reference article on collection expressions or the feature specification.
The addition of ref readonly parameters enables more clarity for APIs that might be
using ref parameters or in parameters:
APIs created before in was introduced might use ref even though the argument
isn't modified. Those APIs can be updated with ref readonly . It won't be a
breaking change for callers, as would be if the ref parameter was changed to in .
An example is System.Runtime.InteropServices.Marshal.QueryInterface.
APIs that take an in parameter, but logically require a variable. A value expression
doesn't work. An example is System.ReadOnlySpan<T>.ReadOnlySpan<T>(T).
APIs that use ref because they require a variable, but don't mutate that variable.
An example is System.Runtime.CompilerServices.Unsafe.IsNullRef.
To learn more about ref readonly parameters, see the article on parameter modifiers in
the language reference, or the ref readonly parameters feature specification.
Default lambda parameters
You can now define default values for parameters on lambda expressions. The syntax
and rules are the same as adding default values for arguments to any method or local
function.
You can learn more about default parameters on lambda expressions in the article on
lambda expressions.
Inline arrays
Inline arrays are used by the runtime team and other library authors to improve
performance in your apps. Inline arrays enable a developer to create an array of fixed
size in a struct type. A struct with an inline buffer should provide performance
characteristics similar to an unsafe fixed size buffer. You likely won't declare your own
inline arrays, but you use them transparently when they're exposed as System.Span<T>
or System.ReadOnlySpan<T> objects from runtime APIs.
C#
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private int _element0;
}
C#
The difference is that the compiler can take advantage of known information about an
inline array. You likely consume inline arrays as you would any other array. For more
information on how to declare inline arrays, see the language reference on struct types.
Experimental attribute
Types, methods, or assemblies can be marked with the
System.Diagnostics.CodeAnalysis.ExperimentalAttribute to indicate an experimental
feature. The compiler issues a warning if you access a method or type annotated with
the ExperimentalAttribute. All types included in an assembly marked with the
Experimental attribute are experimental. You can read more in the article on General
Interceptors
2 Warning
Interceptors are an experimental feature, available in preview mode with C# 12. The
feature may be subject to breaking changes or removal in a future release.
Therefore, it is not recommended for production or released applications.
In order to use interceptors, the user project must specify the property
<InterceptorsPreviewNamespaces> . This is a list of namespaces which are allowed to
contain interceptors.
For example:
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Microsoft.AspN
etCore.Http.Generated;MyLibrary.Generated</InterceptorsPreviewNamespaces>
If you're interested in experimenting with interceptors, you can learn more by reading
the feature specification . If you use the feature, make sure to stay current with any
changes in the feature specification for this experimental feature. If the feature is
finalized, we'll add more guidance on this site.
See also
What's new in .NET 8
6 Collaborate with us on
GitHub .NET feedback
The source for this content can .NET is an open source project.
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
What's new in C# 11
Article • 03/15/2024
You can download the latest .NET 7 SDK from the .NET downloads page . You can also
download Visual Studio 2022 , which includes the .NET 7 SDK.
7 Note
We're interested in your feedback on these features. If you find issues with any of
these new features, create a new issue in the dotnet/roslyn repository.
Generic attributes
You can declare a generic class whose base class is System.Attribute. This feature
provides a more convenient syntax for attributes that require a System.Type parameter.
Previously, you'd need to create an attribute that takes a Type as its constructor
parameter:
C#
// Before C# 11:
public class TypeAttribute : Attribute
{
public TypeAttribute(Type t) => ParamType = t;
C#
[TypeAttribute(typeof(string))]
public string Method() => default;
Using this new feature, you can create a generic attribute instead:
C#
// C# 11 feature:
public class GenericAttribute<T> : Attribute { }
C#
[GenericAttribute<string>()]
public string Method() => default;
You must supply all type parameters when you apply the attribute. In other words, the
generic type must be fully constructed. In the example above, the empty parentheses ( (
and ) ) can be omitted as the attribute does not have any arguments.
C#
The type arguments must satisfy the same restrictions as the typeof operator. Types that
require metadata annotations aren't allowed. For example, the following types aren't
allowed as the type parameter:
dynamic
string? (or any nullable reference type)
(int X, int Y) (or any other tuple types using C# tuple syntax).
These types aren't directly represented in metadata. They include annotations that
describe the type. In all cases, you can use the underlying type instead:
You can add static abstract or static virtual members in interfaces to define
interfaces that include overloadable operators, other static members, and static
properties. The primary scenario for this feature is to use mathematical operators in
generic types. For example, you can implement the System.IAdditionOperators<TSelf,
TOther, TResult> interface in a type that implements operator + . Other interfaces
define other mathematical operations or well-defined values. You can learn about the
new syntax in the article on interfaces. Interfaces that include static virtual methods
are typically generic interfaces. Furthermore, most will declare a constraint that the type
parameter implements the declared interface.
You can learn more and try the feature yourself in the tutorial Explore static abstract
interface members, or the Preview features in .NET 6 – generic math blog post.
unsigned right shift operator: Before C# 11, to force an unsigned right-shift, you
would need to cast any signed integer type to an unsigned type, perform the shift,
then cast the result back to a signed type. Beginning in C# 11, you can use the
>>> , the unsigned shift operator.
You can learn more about the newlines feature in the string interpolations article in the
language reference.
List patterns
List patterns extend pattern matching to match sequences of elements in a list or an
array. For example, sequence is [1, 2, 3] is true when the sequence is an array or a
list of three integers (1, 2, and 3). You can match elements using any pattern, including
constant, type, property and relational patterns. The discard pattern ( _ ) matches any
single element, and the new range pattern ( .. ) matches any sequence of zero or more
elements.
You can learn more details about list patterns in the pattern matching article in the
language reference.
C#
Any whitespace to the left of the closing double quotes will be removed from the string
literal. Raw string literals can be combined with string interpolation to include braces in
the output text. Multiple $ characters denote how many consecutive braces start and
end the interpolation:
C#
The preceding example specifies that two braces start and end an interpolation. The
third repeated opening and closing brace are included in the output string.
You can learn more about raw string literals in the article on strings in the programming
guide, and the language reference articles on string literals and interpolated strings.
Auto-default struct
The C# 11 compiler ensures that all fields of a struct type are initialized to their default
value as part of executing a constructor. This change means any field or auto property
not initialized by a constructor is automatically initialized by the compiler. Structs where
the constructor doesn't definitely assign all fields now compile, and any fields not
explicitly initialized are set to their default value. You can read more about how this
change affects struct initialization in the article on structs.
You can learn more about UTF-8 string literals in the string literal section of the article
on builtin reference types.
Required members
You can add the required modifier to properties and fields to enforce constructors and
callers to initialize those values. The
System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute can be added to
constructors to inform the compiler that a constructor initializes all required members.
For more information on required members, See the init-only section of the properties
article.
You can add the scoped modifier to any ref declaration. This limits the scope where the
reference can escape to.
See also
What's new in .NET 7
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
What's new in C# 10
Article • 02/21/2023
Record structs
Improvements of structure types
Interpolated string handlers
global using directives
File-scoped namespace declaration
Extended property patterns
Improvements on lambda expressions
Allow const interpolated strings
Record types can seal ToString()
Improved definite assignment
Allow both assignment and declaration in the same deconstruction
Allow AsyncMethodBuilder attribute on methods
CallerArgumentExpression attribute
Enhanced #line pragma
Warning wave 6
You can download the latest .NET 6 SDK from the .NET downloads page . You can also
download Visual Studio 2022 , which includes the .NET 6 SDK.
7 Note
We're interested in your feedback on these features. If you find issues with any of
these new features, create a new issue in the dotnet/roslyn repository.
Record structs
You can declare value type records using the record struct or readonly record struct
declarations. You can now clarify that a record is a reference type with the record class
declaration.
C#
namespace MyNamespace;
This new syntax saves both horizontal and vertical space for namespace declarations.
C#
{ Prop1.Prop2: pattern }
is valid in C# 10 and later and equivalent to
C#
For more information, see the Extended property patterns feature proposal note. For
more information about a property pattern, see the Property pattern section of the
Patterns article.
Lambda expressions may have a natural type, where the compiler can infer a
delegate type from the lambda expression or method group.
Lambda expressions may declare a return type when the compiler can't infer it.
Attributes can be applied to lambda expressions.
These features make lambda expressions more similar to methods and local functions.
They make it easier to use lambda expressions without declaring a variable of a delegate
type, and they work more seamlessly with the new ASP.NET Core Minimal APIs.
C#
// Initialization:
(int x, int y) = point;
// assignment:
int x1 = 0;
int y1 = 0;
(x1, y1) = point;
C#
int x = 0;
(x, int y) = point;
C#
// Or, using ?.
if (c?.GetDependentValue(out object obj) == true)
{
representation = obj.ToString(); // undesired error
}
// Or, using ??
if (c?.GetDependentValue(out object obj) ?? false)
{
representation = obj.ToString(); // undesired error
}
The main impact of this improvement is that the warnings for definite assignment and
null-state analysis are more accurate.
To learn more, see the section on AsyncMethodBuilder in the article on attributes read
by the compiler.
C#
You can learn more about this feature in the article on Caller information attributes in
the language reference section.
Enhanced #line pragma
C# 10 supports a new format for the #line pragma. You likely won't use the new format,
but you'll see its effects. The enhancements enable more fine-grained output in domain-
specific languages (DSLs) like Razor. The Razor engine uses these enhancements to
improve the debugging experience. You'll find debuggers can highlight your Razor
source more accurately. To learn more about the new syntax, see the article on
Preprocessor directives in the language reference. You can also read the feature
specification for Razor based examples.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Learn about any breaking changes in
the C# compiler
Article • 11/18/2022
The Roslyn team maintains a list of breaking changes in the C# and Visual Basic
compilers. You can find information on those changes at these links on their GitHub
repository:
This article provides a history of each major release of the C# language. The C# team is
continuing to innovate and add new features. Detailed language feature status,
including features considered for upcoming releases can be found on the dotnet/roslyn
repository on GitHub.
) Important
The C# language relies on types and methods in what the C# specification defines
as a standard library for some of the features. The .NET platform delivers those
types and methods in a number of packages. One example is exception processing.
Every throw statement or expression is checked to ensure the object being thrown
is derived from Exception. Similarly, every catch is checked to ensure that the type
being caught is derived from Exception. Each version may add new requirements.
To use the latest language features in older environments, you may need to install
specific libraries. These dependencies are documented in the page for each specific
version. You can learn more about the relationships between language and library
for background on this dependency.
C# version 12
Released November 2023
Primary constructors - You can create primary constructors in any class or struct
type.
Collection expressions - A new syntax to specify collection expressions, including
the spread element, ( ..e ), to expand any collection.
Inline arrays - Inline arrays enable you to create an array of fixed size in a struct
type.
Optional parameters in lambda expressions - You can define default values for
parameters on lambda expressions.
ref readonly parameters - ref readonly parameters enables more clarity for APIs
that might be using ref parameters or in parameters.
Alias any type - You can use the using alias directive to alias any type, not just
named types.
Experimental attribute - Indicate an experimental feature.
Overall, C# 12 provides new features that make you more productive writing C# code.
Syntax you already knew is available in more places. Other syntax enables consistency
for related concepts.
C# version 11
Released November 2022
C# 11 introduces generic math and several features that support that goal. You can write
numeric algorithms once for all number types. There's more features to make working
with struct types easier, like required members and auto-default structs. Working with
strings gets easier with Raw string literals, newline in string interpolations, and UTF-8
string literals. Features like file local types enable source generators to be simpler.
Finally, list patterns add more support for pattern matching.
C# version 10
Released November 2021
More features were available in preview mode. In order to use these features, you must
set <LangVersion> to Preview in your project:
Many of the features mean you type less code to express the same concepts. Record
structs synthesize many of the same methods that record classes do. Structs and
anonymous types support with expressions. Global using directives and file scoped
namespace declarations mean you express dependencies and namespace organization
more clearly. Lambda improvements make it easier to declare lambda expressions where
they're used. New property patterns and deconstruction improvements create more
concise code.
The new interpolated string handlers and AsyncMethodBuilder behavior can improve
performance. These language features were applied in the .NET Runtime to achieve
performance improvements in .NET 6.
C# 10 also marks more of a shift to the yearly cadence for .NET releases. Because not
every feature can be completed in a yearly timeframe, you can try a couple of "preview"
features in C# 10. Both generic attributes and static abstract members in interfaces can be
used, but these preview features might change before their final release.
C# version 9
Released November 2020
C# 9 was released with .NET 5. It's the default language version for any assembly that
targets the .NET 5 release. It contains the following new and enhanced features:
Records
Init only setters
Top-level statements
Pattern matching enhancements: relational patterns and logical patterns
Performance and interop
Native sized integers
Function pointers
Suppress emitting localsinit flag
Module initializers
New features for partial methods
Fit and finish features
Target-typed new expressions
static anonymous functions
Target-typed conditional expressions
Covariant return types
Extension GetEnumerator support for foreach loops
Lambda discard parameters
Attributes on local functions
Top level statements means your main program is simpler to read. There's less need for
ceremony: a namespace, a Program class, and static void Main() are all unnecessary.
The introduction of records provides a concise syntax for reference types that follow
value semantics for equality. You use these types to define data containers that typically
define minimal behavior. Init-only setters provide the capability for nondestructive
mutation ( with expressions) in records. C# 9 also adds covariant return types so that
derived records can override virtual methods and return a type derived from the base
method's return type.
The pattern matching capabilities expanded in several ways. Numeric types now support
range patterns. Patterns can be combined using and , or , and not patterns. Parentheses
can be added to clarify more complex patterns:
These patterns enrich the syntax for patterns. Consider these examples:
C#
With optional parentheses to make it clear that and has higher precedence than or :
C#
One of the most common uses is a new syntax for a null check:
C#
if (e is not null)
{
// ...
}
Any of these patterns can be used in any context where patterns are allowed: is pattern
expressions, switch expressions, nested patterns, and the pattern of a switch
statement's case label.
The nint and nuint types model the native-size integer types on the target CPU.
Function pointers provide delegate-like functionality while avoiding the allocations
necessary to create a delegate object.
The localsinit instruction can be omitted to save instructions.
Performance and interop
Another set of improvements supports scenarios where code generators add
functionality:
Module initializers are methods that the runtime calls when an assembly loads.
Partial methods support new accessibly modifiers and non-void return types. In
those cases, an implementation must be provided.
C# version 8.0
Released September 2019
C# 8.0 is the first major C# release that specifically targets .NET Core. Some features rely
on new Common Language Runtime (CLR) capabilities, others on library types added
only in .NET Core. C# 8.0 adds the following features and enhancements to the C#
language:
Readonly members
Default interface methods
Pattern matching enhancements:
Switch expressions
Property patterns
Tuple patterns
Positional patterns
Using declarations
Static local functions
Disposable ref structs
Nullable reference types
Asynchronous streams
Indices and ranges
Null-coalescing assignment
Unmanaged constructed types
Stackalloc in nested expressions
Enhancement of interpolated verbatim strings
Default interface members require enhancements in the CLR. Those features were added
in the CLR for .NET Core 3.0. Ranges and indexes, and asynchronous streams require
new types in the .NET Core 3.0 libraries. Nullable reference types, while implemented in
the compiler, is much more useful when libraries are annotated to provide semantic
information regarding the null state of arguments and return values. Those annotations
are being added in the .NET Core libraries.
C# version 7.3
Released May 2018
There are two main themes to the C# 7.3 release. One theme provides features that
enable safe code to be as performant as unsafe code. The second theme provides
incremental improvements to existing features. New compiler options were also added
in this release.
The following new features support the theme of better performance for safe code:
C# version 7.2
Released November 2017
C# version 7.1
Released August 2017
C# started releasing point releases with C# 7.1. This version added the language version
selection configuration element, three new language features, and new compiler
behavior.
Finally, the compiler has two options -refout and -refonly that control reference
assembly generation.
C# version 7.0
Released March 2017
C# version 7.0 was released with Visual Studio 2017. This version has some evolutionary
and cool stuff in the vein of C# 6.0. Here are some of the new features:
Out variables
Tuples and deconstruction
Pattern matching
Local functions
Expanded expression bodied members
Ref locals
Ref returns
Discards
Binary Literals and Digit Separators
Throw expressions
All of these features offer new capabilities for developers and the opportunity to write
cleaner code than ever. A highlight is condensing the declaration of variables to use
with the out keyword and by allowing multiple return values via tuple. .NET Core now
targets any operating system and has its eyes firmly on the cloud and on portability.
These new capabilities certainly occupy the language designers' thoughts and time, in
addition to coming up with new features.
C# version 6.0
Released July 2015
Version 6.0, released with Visual Studio 2015, released many smaller features that made
C# programming more productive. Here are some of them:
Static imports
Exception filters
Auto-property initializers
Expression bodied members
Null propagator
String interpolation
nameof operator
Index initializers
Await in catch/finally blocks
Default values for getter-only properties
If you look at these features together, you see an interesting pattern. In this version, C#
started to eliminate language boilerplate to make code more terse and readable. So for
fans of clean, simple code, this language version was a huge win.
They did one other thing along with this version, though it's not a traditional language
feature in itself. They released Roslyn the compiler as a service . The C# compiler is
now written in C#, and you can use the compiler as part of your programming efforts.
C# version 5.0
Released August 2012
C# version 5.0, released with Visual Studio 2012, was a focused version of the language.
Nearly all of the effort for that version went into another groundbreaking language
concept: the async and await model for asynchronous programming. Here's the major
features list:
Asynchronous members
Caller info attributes
Code Project: Caller Info Attributes in C# 5.0
The caller info attribute lets you easily retrieve information about the context in which
you're running without resorting to a ton of boilerplate reflection code. It has many uses
in diagnostics and logging tasks.
But async and await are the real stars of this release. When these features came out in
2012, C# changed the game again by baking asynchrony into the language as a first-
class participant.
C# version 4.0
Released April 2010
C# version 4.0, released with Visual Studio 2010, introduced some interesting new
features:
Dynamic binding
Named/optional arguments
Generic covariant and contravariant
Embedded interop types
Embedded interop types eased the deployment pain of creating COM interop
assemblies for your application. Generic covariance and contravariance give you more
power to use generics, but they're a bit academic and probably most appreciated by
framework and library authors. Named and optional parameters let you eliminate many
method overloads and provide convenience. But none of those features are exactly
paradigm altering.
The major feature was the introduction of the dynamic keyword. The dynamic keyword
introduced into C# version 4.0 the ability to override the compiler on compile-time
typing. By using the dynamic keyword, you can create constructs similar to dynamically
typed languages like JavaScript. You can create a dynamic x = "a string" and then add
six to it, leaving it up to the runtime to sort out what should happen next.
Dynamic binding gives you the potential for errors but also great power within the
language.
C# version 3.0
Released November 2007
C# version 3.0 came in late 2007, along with Visual Studio 2008, though the full boat of
language features would actually come with .NET Framework version 3.5. This version
marked a major change in the growth of C#. It established C# as a truly formidable
programming language. Let's take a look at some major features in this version:
Auto-implemented properties
Anonymous types
Query expressions
Lambda expressions
Expression trees
Extension methods
Implicitly typed local variables
Partial methods
Object and collection initializers
In retrospect, many of these features seem both inevitable and inseparable. They all fit
together strategically. This C# version's killer feature was the query expression, also
known as Language-Integrated Query (LINQ).
A more nuanced view examines expression trees, lambda expressions, and anonymous
types as the foundation upon which LINQ is constructed. But, in either case, C# 3.0
presented a revolutionary concept. C# 3.0 began to lay the groundwork for turning C#
into a hybrid Object-Oriented / Functional language.
Specifically, you could now write SQL-style, declarative queries to perform operations on
collections, among other things. Instead of writing a for loop to compute the average
of a list of integers, you could now do that as simply as list.Average() . The
combination of query expressions and extension methods made a list of integers a
whole lot smarter.
C# version 2.0
Released November 2005
Let's take a look at some major features of C# 2.0, released in 2005, along with Visual
Studio 2005:
Generics
Partial types
Anonymous methods
Nullable value types
Iterators
Covariance and contravariance
iterate through them. Using generics is better than creating a ListInt type that derives
from ArrayList or casting from Object for every operation.
C# version 2.0 brought iterators. To put it succinctly, iterators let you examine all the
items in a List (or other Enumerable types) with a foreach loop. Having iterators as a
first-class part of the language dramatically enhanced readability of the language and
people's ability to reason about the code.
C# version 1.2
Released April 2003
C# version 1.2 shipped with Visual Studio .NET 2003. It contained a few small
enhancements to the language. Most notable is that starting with this version, the code
generated in a foreach loop called Dispose on an IEnumerator when that IEnumerator
implemented IDisposable.
C# version 1.0
Released January 2002
When you go back and look, C# version 1.0, released with Visual Studio .NET 2002,
looked a lot like Java. As part of its stated design goals for ECMA , it sought to be a
"simple, modern, general-purpose object-oriented language." At the time, looking like
Java meant it achieved those early design goals.
But if you look back on C# 1.0 now, you'd find yourself a little dizzy. It lacked the built-in
async capabilities and some of the slick functionality around generics you take for
granted. As a matter of fact, it lacked generics altogether. And LINQ? Not available yet.
Those additions would take some years to come out.
C# version 1.0 looked stripped of features, compared to today. You'd find yourself
writing some verbose code. But yet, you have to start somewhere. C# version 1.0 was a
viable alternative to Java on the Windows platform.
Classes
Structs
Interfaces
Events
Properties
Delegates
Operators and expressions
Statements
Attributes
Article originally published on the NDepend blog , courtesy of Erik Dietrich and Patrick
Smacchia.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Relationships between language
features and library types
Article • 10/26/2023
The C# language definition requires a standard library to have certain types and certain
accessible members on those types. The compiler generates code that uses these
required types and members for many different language features. For this reason, C#
versions are supported only for the corresponding .NET version and newer. That ensures
the correct run-time behavior and the availability of all required types and members.
This dependency on standard library functionality has been part of the C# language
since its first version. In that version, examples included:
That first version was simple: the compiler and the standard library shipped together,
and there was only one version of each.
The language design team works to minimize the surface area of the types and
members required in a compliant standard library. That goal is balanced against a clean
design where new library features are incorporated seamlessly into the language. There
will be new features in future versions of C# that require new types and members in a
standard library. C# compiler tools are now decoupled from the release cycle of the .NET
libraries on supported platforms.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
issues and pull requests. For Open a documentation issue
more information, see our
contributor guide. Provide product feedback
Version and update considerations for
C# developers
Article • 06/29/2023
The language version used to compile your app typically matches the runtime target
framework moniker (TFM) referenced in your project. For more information on changing
the default language version, see the article titled configure your language version. This
default behavior ensures maximum compatibility.
When a binary breaking change affects your app, you must recompile your app, but you
don't need to edit any source code. When a source breaking change affects your app,
the existing binary still runs correctly in environments with the updated runtime and
libraries. However, you must make source changes to recompile with the new language
version and runtime. If a change is both source breaking and binary breaking, you must
recompile your application with the latest version and make source updates.
Because of the goal to avoid breaking changes by the C# language team and runtime
team, updating your application is typically a matter of updating the TFM and rebuilding
the app. However, for libraries that are distributed publicly, you should carefully evaluate
your policy for supported TFMs and supported language versions. You may be creating
a new library with features found in the latest version and need to ensure apps built
using previous versions of the compiler can use it. Or you may be upgrading an existing
library and many of your users might not have upgraded versions yet.
Introducing breaking changes in your libraries
When you adopt new language features in your library's public API, you should evaluate
if adopting the feature introduces either a binary or source breaking change for the
users of your library. Any changes to your internal implementation that don't appear in
the public or protected interfaces are compatible.
7 Note
A binary breaking change requires your users to recompile their code in order to use the
new version. For example, consider this public method:
C#
If you add the in modifier to the method, that's a binary breaking change:
C#
Users must recompile any application that uses the CalculateSquare method for the
new library to work correctly.
A source breaking change requires your users to change their code before they
recompile. For example, consider this type:
C#
C#
The previous change requires changes for any type derived from Person . All those
declarations must add the record modifier to their declarations.
When you make a source breaking change to your library, you require all projects to
make source changes in order to use your new library. If the necessary change requires
new language features, you force those projects to upgrade to the same language
version and TFM you're now using. You've required more work for your users, and
possibly forced them to upgrade as well.
The impact of any breaking change you make depends on the number of projects that
have a dependency on your library. If your library is used internally by a few
applications, you can react to any breaking changes in all impacted projects. However, if
your library is publicly downloaded, you should evaluate the potential impact and
consider alternatives:
Prerequisites
You need to set up your machine to run .NET 8 or later, including the C# 12 or later
compiler. The C# 12 compiler is available starting with Visual Studio 2022 version 17.7
or the .NET 8 SDK .
Primary constructors
You can add parameters to a struct or class declaration to create a primary
constructor. Primary constructor parameters are in scope throughout the class definition.
It's important to view primary constructor parameters as parameters even though they
are in scope throughout the class definition. Several rules clarify that they're parameters:
These rules are the same as parameters to any method, including other constructor
declarations.
Initialize property
The following code initializes two readonly properties that are computed from primary
constructor parameters:
C#
C#
The new feature makes it easier to use field initializers when you need arguments to
initialize a field or property.
C#
In the preceding example, the Translate method changes the dx and dy components.
That requires the Magnitude and Direction properties be computed when accessed. The
=> operator designates an expression-bodied get accessor, whereas the = operator
designates an initializer. This version adds a parameterless constructor to the struct. The
parameterless constructor must invoke the primary constructor, so that all the primary
constructor parameters are initialized.
In the previous example, the primary constructor properties are accessed in a method.
Therefore the compiler creates hidden fields to represent each parameter. The following
code shows approximately what the compiler generates. The actual field names are valid
CIL identifiers, but not valid C# identifiers.
C#
It's important to understand that the first example didn't require the compiler to create
a field to store the value of the primary constructor parameters. The second example
used the primary constructor parameter inside a method, and therefore required the
compiler to create storage for them. The compiler creates storage for any primary
constructors only when that parameter is accessed in the body of a member of your
type. Otherwise, the primary constructor parameters aren't stored in the object.
Dependency injection
Another common use for primary constructors is to specify parameters for dependency
injection. The following code creates a simple controller that requires a service interface
for its use:
C#
The primary constructor clearly indicates the parameters needed in the class. You use
the primary constructor parameters as you would any other variable in the class.
C#
All bank accounts, regardless of the type, have properties for the account number and
an owner. In the completed application, other common functionality would be added to
the base class.
Many types require more specific validation on constructor parameters. For example, the
BankAccount has specific requirements for the owner and accountID parameters: The
owner must not be null or whitespace, and the accountID must be a string containing
10 digits. You can add this validation when you assign the corresponding properties:
C#
The previous example shows how you can validate the constructor parameters before
assigning them to the properties. You can use builtin methods, like
String.IsNullOrWhiteSpace(String), or your own validation method, like
ValidAccountNumber . In the previous example, any exceptions are thrown from the
constructor, when it invokes the initializers. If a constructor parameter isn't used to
assign a field, any exceptions are thrown when the constructor parameter is first
accessed.
C#
The derived CheckingAccount class has a primary constructor that takes all the
parameters needed in the base class, and another parameter with a default value. The
primary constructor calls the base constructor using the : BankAccount(accountID,
owner) syntax. This expression specifies both the type for the base class, and the
Your derived class isn't required to use a primary constructor. You can create a
constructor in the derived class that invokes the base class' primary constructor, as
shown in the following example:
C#
There's one potential concern with class hierarchies and primary constructors: it's
possible to create multiple copies of a primary constructor parameter as it's used in
both derived and base classes. The following code example creates two copies each of
the owner and accountID field:
C#
public class SavingsAccount(string accountID, string owner, decimal
interestRate) : BankAccount(accountID, owner)
{
public SavingsAccount() : this("default", "default", 0.01m) { }
public decimal CurrentBalance { get; private set; } = 0;
The highlighted line shows that the ToString method uses the primary constructor
parameters ( owner and accountID ) rather than the base class properties ( Owner and
AccountID ). The result is that the derived class, SavingsAccount creates storage for those
copies. The copy in the derived class is different than the property in the base class. If
the base class property could be modified, the instance of the derived class won't see
that modification. The compiler issues a warning for primary constructor parameters
that are used in a derived class and passed to a base class constructor. In this instance,
the fix is to use the properties of the base class.
Summary
You can use the primary constructors as best suits your design. For classes and structs,
primary constructor parameters are parameters to a constructor that must be invoked.
You can use them to initialize properties. You can initialize fields. Those properties or
fields can be immutable, or mutable. You can use them in methods. They're parameters,
and you use them in what manner suits your design best. You can learn more about
primary constructors in the C# programming guide article on instance constructors and
the proposed primary constructor specification.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Tutorial: Explore C# 11 feature - static
virtual members in interfaces
Article • 08/03/2022
C# 11 and .NET 7 include static virtual members in interfaces. This feature enables you to
define interfaces that include overloaded operators or other static members. Once
you've defined interfaces with static members, you can use those interfaces as
constraints to create generic types that use operators or other static methods. Even if
you don't create interfaces with overloaded operators, you'll likely benefit from this
feature and the generic math classes enabled by the language update.
Prerequisites
You'll need to set up your machine to run .NET 7, which supports C# 11. The C# 11
compiler is available starting with Visual Studio 2022, version 17.3 or the .NET 7
SDK .
C#
The same logic would work for any numeric type: int , short , long , float decimal , or
any type that represents a number. You need to have a way to use the + and /
operators, and to define a value for 2 . You can use the
System.Numerics.INumber<TSelf> interface to write the preceding method as the
following generic method:
C#
Any type that implements the INumber<TSelf> interface must include a definition for
operator + , and for operator / . The denominator is defined by T.CreateChecked(2) to
create the value 2 for any numeric type, which forces the denominator to be the same
type as the two parameters. INumberBase<TSelf>.CreateChecked<TOther>(TOther)
creates an instance of the type from the specified value and throws an
OverflowException if the value falls outside the representable range. (This
implementation has the potential for overflow if left and right are both large enough
values. There are alternative algorithms that can avoid this potential issue.)
You define static abstract members in an interface using familiar syntax: You add the
static and abstract modifiers to any static member that doesn't provide an
implementation. The following example defines an IGetNext<T> interface that can be
applied to any type that overrides operator ++ :
C#
The constraint that the type argument, T , implements IGetNext<T> ensures that the
signature for the operator includes the containing type, or its type argument. Many
operators enforce that its parameters must match the type, or be the type parameter
constrained to implement the containing type. Without this constraint, the ++ operator
couldn't be defined in the IGetNext<T> interface.
You can create a structure that creates a string of 'A' characters where each increment
adds another character to the string using the following code:
C#
public RepeatSequence() {}
public static RepeatSequence operator ++(RepeatSequence other)
=> other with { Text = other.Text + Ch };
More generally, you can build any algorithm where you might want to define ++ to
mean "produce the next value of this type". Using this interface produces clear code and
results:
C#
PowerShell
A
AA
AAA
AAAA
AAAAA
AAAAAA
AAAAAAA
AAAAAAAA
AAAAAAAAA
AAAAAAAAAA
This small example demonstrates the motivation for this feature. You can use natural
syntax for operators, constant values, and other static operations. You can explore these
techniques when you create multiple types that rely on static members, including
overloaded operators. Define the interfaces that match your types' capabilities and then
declare those types' support for the new interface.
Generic math
The motivating scenario for allowing static methods, including operators, in interfaces is
to support generic math algorithms. The .NET 7 base class library contains interface
definitions for many arithmetic operators, and derived interfaces that combine many
arithmetic operators in an INumber<T> interface. Let's apply those types to build a
Point<T> record that can use any numeric type for T . The point can be moved by some
XOffset and YOffset using the + operator.
Start by creating a new Console application, either by using dotnet new or Visual Studio.
The public interface for the Translation<T> and Point<T> should look like the following
code:
C#
You use the record type for both the Translation<T> and Point<T> types: Both store
two values, and they represent data storage rather than sophisticated behavior. The
implementation of operator + would look like the following code:
C#
For the previous code to compile, you'll need to declare that T supports the
IAdditionOperators<TSelf, TOther, TResult> interface. That interface includes the
operator + static method. It declares three type parameters: One for the left operand,
one for the right operand, and one for the result. Some types implement + for different
operand and result types. Add a declaration that the type argument, T implements
IAdditionOperators<T, T, T> :
C#
After you add that constraint, your Point<T> class can use the + for its addition
operator. Add the same constraint on the Translation<T> declaration:
C#
public record Translation<T>(T XOffset, T YOffset) where T :
IAdditionOperators<T, T, T>;
The IAdditionOperators<T, T, T> constraint prevents a developer using your class from
creating a Translation using a type that doesn't meet the constraint for the addition to
a point. You've added the necessary constraints to the type parameter for
Translation<T> and Point<T> so this code works. You can test by adding code like the
following above the declarations of Translation and Point in your Program.cs file:
C#
Console.WriteLine(pt);
Console.WriteLine(translate);
Console.WriteLine(final);
You can make this code more reusable by declaring that these types implement the
appropriate arithmetic interfaces. The first change to make is to declare that Point<T,
T> implements the IAdditionOperators<Point<T>, Translation<T>, Point<T>> interface.
The Point type makes use of different types for operands and the result. The Point
type already implements an operator + with that signature, so adding the interface to
the declaration is all you need:
C#
Finally, when you're performing addition, it's useful to have a property that defines the
additive identity value for that type. There's a new interface for that feature:
IAdditiveIdentity<TSelf,TResult>. A translation of {0, 0} is the additive identity: The
resulting point is the same as the left operand. The IAdditiveIdentity<TSelf, TResult>
interface defines one readonly property, AdditiveIdentity , that returns the identity
value. The Translation<T> needs a few changes to implement this interface:
C#
using System.Numerics;
There are a few changes here, so let's walk through them one by one. First, you declare
that the Translation type implements the IAdditiveIdentity interface:
C#
You next might try implementing the interface member as shown in the following code:
C#
The preceding code won't compile, because 0 depends on the type. The answer: Use
IAdditiveIdentity<T>.AdditiveIdentity for 0 . That change means that your constraints
C#
Now that you've added that constraint on Translation<T> , you need to add the same
constraint to Point<T> :
C#
using System.Numerics;
This sample has given you a look at how the interfaces for generic math compose. You
learned how to:
" Write a method that relied on the INumber<T> interface so that method could be
used with any numeric type.
" Build a type that relies on the addition interfaces to implement a type that only
supports one mathematical operation. That type declares its support for those same
interfaces so it can be composed in other ways. The algorithms are written using
the most natural syntax of mathematical operators.
Experiment with these features and register feedback. You can use the Send Feedback
menu item in Visual Studio, or create a new issue in the roslyn repository on GitHub.
Build generic algorithms that work with any numeric type. Build algorithms using these
interfaces where the type argument may only implement a subset of number-like
capabilities. Even if you don't build new interfaces that use these capabilities, you can
experiment with using them in your algorithms.
See also
Generic math
6 Collaborate with us on
GitHub .NET feedback
The source for this content can .NET is an open source project.
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Use pattern matching to build your class
behavior for better code
Article • 12/06/2022
The pattern matching features in C# provide syntax to express your algorithms. You can
use these techniques to implement the behavior in your classes. You can combine
object-oriented class design with a data-oriented implementation to provide concise
code while modeling real-world objects.
Prerequisites
You'll need to set up your machine to run .NET. Download Visual Studio 2022 or the
.NET SDK .
In its normal operation, a boat enters one of the gates while the water level in the lock
matches the water level on the side the boat enters. Once in the lock, the water level is
changed to match the water level where the boat will leave the lock. Once the water
level matches that side, the gate on the exit side opens. Safety measures make sure an
operator can't create a dangerous situation in the canal. The water level can be changed
only when both gates are closed. At most one gate can be open. To open a gate, the
water level in the lock must match the water level outside the gate being opened.
You can build a C# class to model this behavior. A CanalLock class would support
commands to open or close either gate. It would have other commands to raise or lower
the water. The class should also support properties to read the current state of both
gates and the water level. Your methods implement the safety measures.
Define a class
You'll build a console application to test your CanalLock class. Create a new console
project for .NET 5 using either Visual Studio or the .NET CLI. Then, add a new class and
name it CanalLock . Next, design your public API, but leave the methods not
implemented:
C#
The preceding code initializes the object so both gates are closed, and the water level is
low. Next, write the following test code in your Main method to guide you as you create
a first implementation of the class:
C#
canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate: {canalGate}");
canalGate.SetLowGate(open: false);
Console.WriteLine($"Close the lower gate: {canalGate}");
canalGate.SetWaterLevel(WaterLevel.High);
Console.WriteLine($"Raise the water level: {canalGate}");
canalGate.SetHighGate(open: true);
Console.WriteLine($"Open the higher gate: {canalGate}");
canalGate.SetHighGate(open: false);
Console.WriteLine($"Close the higher gate: {canalGate}");
canalGate.SetWaterLevel(WaterLevel.Low);
Console.WriteLine($"Lower the water level: {canalGate}");
canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate: {canalGate}");
canalGate.SetLowGate(open: false);
Console.WriteLine($"Close the lower gate: {canalGate}");
Next, add a first implementation of each method in the CanalLock class. The following
code implements the methods of the class without concern to the safety rules. You'll
add safety tests later:
C#
The tests you've written so far pass. You've implemented the basics. Now, write a test for
the first failure condition. At the end of the previous tests, both gates are closed, and
the water level is set to low. Add a test to try opening the upper gate:
C#
Console.WriteLine("=============================================");
Console.WriteLine(" Test invalid commands");
// Open "wrong" gate (2 tests)
try
{
canalGate = new CanalLock();
canalGate.SetHighGate(open: true);
}
catch (InvalidOperationException)
{
Console.WriteLine("Invalid operation: Can't open the high gate. Water is
low.");
}
Console.WriteLine($"Try to open upper gate: {canalGate}");
This test fails because the gate opens. As a first implementation, you could fix it with the
following code:
C#
Your tests pass. But, as you add more tests, you'll add more and more if clauses and
test different properties. Soon, these methods will get too complicated as you add more
conditionals.
ノ Expand table
The fourth and last rows in the table have strike through text because they're invalid.
The code you're adding now should make sure the high water gate is never opened
when the water is low. Those states can be coded as a single switch expression
(remember that false indicates "Closed"):
C#
C#
The preceding switch arm must be last in your switch expression because it matches all
inputs. Experiment by moving it earlier in the order. That causes a compiler error CS8510
for unreachable code in a pattern. The natural structure of switch expressions enables
the compiler to generate errors and warnings for possible mistakes. The compiler "safety
net" makes it easier for you to create correct code in fewer iterations, and the freedom
to combine switch arms with wildcards. The compiler will issue errors if your
combination results in unreachable arms you didn't expect, and warnings if you remove
an arm that's needed.
The first change is to combine all the arms where the command is to close the gate;
that's always allowed. Add the following code as the first arm in your switch expression:
C#
After you add the previous switch arm, you'll get four compiler errors, one on each of
the arms where the command is false . Those arms are already covered by the newly
added arm. You can safely remove those four lines. You intended this new switch arm to
replace those conditions.
Next, you can simplify the four arms where the command is to open the gate. In both
cases where the water level is high, the gate can be opened. (In one, it's already open.)
One case where the water level is low throws an exception, and the other shouldn't
happen. It should be safe to throw the same exception if the water lock is already in an
invalid state. You can make the following simplifications for those arms:
C#
Run your tests again, and they pass. Here's the final version of the SetHighGate method:
C#
C#
Console.WriteLine();
Console.WriteLine();
try
{
canalGate = new CanalLock();
canalGate.SetWaterLevel(WaterLevel.High);
canalGate.SetLowGate(open: true);
}
catch (InvalidOperationException)
{
Console.WriteLine("invalid operation: Can't open the lower gate. Water
is high.");
}
Console.WriteLine($"Try to open lower gate: {canalGate}");
// change water level with gate open (2 tests)
Console.WriteLine();
Console.WriteLine();
try
{
canalGate = new CanalLock();
canalGate.SetLowGate(open: true);
canalGate.SetWaterLevel(WaterLevel.High);
}
catch (InvalidOperationException)
{
Console.WriteLine("invalid operation: Can't raise water when the lower
gate is open.");
}
Console.WriteLine($"Try to raise water with lower gate open: {canalGate}");
Console.WriteLine();
Console.WriteLine();
try
{
canalGate = new CanalLock();
canalGate.SetWaterLevel(WaterLevel.High);
canalGate.SetHighGate(open: true);
canalGate.SetWaterLevel(WaterLevel.Low);
}
catch (InvalidOperationException)
{
Console.WriteLine("invalid operation: Can't lower water when the high
gate is open.");
}
Console.WriteLine($"Try to lower water with high gate open: {canalGate}");
Run your application again. You can see the new tests fail, and the canal lock gets into
an invalid state. Try to implement the remaining methods yourself. The method to set
the lower gate should be similar to the method to set the upper gate. The method that
changes the water level has different checks, but should follow a similar structure. You
may find it helpful to use the same process for the method that sets the water level.
Start with all four inputs: The state of both gates, the current state of the water level,
and the requested new water level. The switch expression should start with:
C#
You'll have 16 total switch arms to fill in. Then, test and simplify.
Your tests should pass, and the canal lock should operate safely.
Summary
In this tutorial, you learned to use pattern matching to check the internal state of an
object before applying any changes to that state. You can check combinations of
properties. Once you've built tables for any of those transitions, you test your code, then
simplify for readability and maintainability. These initial refactorings may suggest further
refactorings that validate internal state or manage other API changes. This tutorial
combined classes and objects with a more data-oriented, pattern-based approach to
implement those classes.
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project.
The source for this content can
Select a link to provide feedback:
be found on GitHub, where you
can also create and review
Open a documentation issue
issues and pull requests. For
more information, see our
Provide product feedback
contributor guide.
Tutorial: Write a custom string
interpolation handler
Article • 04/06/2023
Prerequisites
You'll need to set up your machine to run .NET 6, including the C# 10 compiler. The C#
10 compiler is available starting with Visual Studio 2022 or .NET 6 SDK .
This tutorial assumes you're familiar with C# and .NET, including either Visual Studio or
the .NET CLI.
New outline
C# 10 adds support for a custom interpolated string handler. An interpolated string
handler is a type that processes the placeholder expression in an interpolated string.
Without a custom handler, placeholders are processed similar to String.Format. Each
placeholder is formatted as text, and then the components are concatenated to form
the resulting string.
You can write a handler for any scenario where you use information about the resulting
string. Will it be used? What constraints are on the format? Some examples include:
You may require none of the resulting strings are greater than some limit, such as
80 characters. You can process the interpolated strings to fill a fixed-length buffer,
and stop processing once that buffer length is reached.
You may have a tabular format, and each placeholder must have a fixed length. A
custom handler can enforce that, rather than forcing all client code to conform.
In this tutorial, you'll create a string interpolation handler for one of the core
performance scenarios: logging libraries. Depending on the configured log level, the
work to construct a log message isn't needed. If logging is off, the work to construct a
string from an interpolated string expression isn't needed. The message is never printed,
so any string concatenation can be skipped. In addition, any expressions used in the
placeholders, including generating stack traces, doesn't need to be done.
An interpolated string handler can determine if the formatted string will be used, and
only perform the necessary work if needed.
Initial implementation
Let's start from a basic Logger class that supports different levels:
C#
This Logger supports six different levels. When a message won't pass the log level filter,
there's no output. The public API for the logger accepts a (fully formatted) string as the
message. All the work to create the string has already been done.
Internally, the builder creates the formatted string, and provides a member for a client
to retrieve that string. The following code shows a LogInterpolatedStringHandler type
that meets these requirements:
C#
[InterpolatedStringHandler]
public ref struct LogInterpolatedStringHandler
{
// Storage for the built-up string
StringBuilder builder;
builder.Append(s);
Console.WriteLine($"\tAppended the literal string");
}
builder.Append(t?.ToString());
Console.WriteLine($"\tAppended the formatted object");
}
You can now add an overload to LogMessage in the Logger class to try your new
interpolated string handler:
C#
public void LogMessage(LogLevel level, LogInterpolatedStringHandler builder)
{
if (EnabledLevel < level) return;
Console.WriteLine(builder.GetFormattedText());
}
You don't need to remove the original LogMessage method, the compiler will prefer a
method with an interpolated handler parameter over a method with a string parameter
when the argument is an interpolated string expression.
You can verify that the new handler is invoked using the following code as the main
program:
C#
PowerShell