Basic Syntax
Basic Setup
Tooling
- VSCode
- Install C# Dev Kit extension
- Install .NET SDK
dotnet
commands
dotnet new console -n LearnCSharp
- create a new project calledLearnCSharp
.console
is fine for C#. Later, this can be replaced to create ASP.NET, Blazor, Maui applications.dotnet build
- build an executable.dotnet run
- build and run the executable (use this during development).
Folder structure
LearnCSharp.csproj
- Used by .NET to get information on how to build the project (can ignore).LearnCSharp.sln
- Specific to Visual Studio (Code) and is used for organizing projects. Multiple projects can be maintained together by using a solution file. (can ignore). docsProgram.cs
- Entry point fordotnet run
.
VSCode specific
- In bottom-left expand Solution Explorer. Reads
LearnCSharp.sln
file, to create this view. docs
Main method (entry point)
-
Hello World example
-
By default
LearnCSharp
namespace is created. Sonamespace LearnCSharp
can be skipped.Namespace details
-
Option 1. Change default namespace name. Specify the name of new namespace in
LearnCSharp.csproj
as<RootNamespace>YourNameSpace</RootNamespace>
-
Option 2. Although adding
<RootNameSpace>
toLearnCSharp.csproj
can be skipped. This is not recommended, since this breaks C# tooling. C# tooling expects namespace to beLearnCSharp.FolderName
(as shown in Option 3). -
Option 3. Create
YourNameSpace
folder withtemp.cs
. The namespace can now be changed toLearnCSharp.YourNameSpace
.Call the
Function
fromMain
Namespaces are used to organize and group related classes, primarily to avoid name conflicts when multiple classes with the same name exist in different parts of the project. Unlike Node.js, where functions or modules must be explicitly imported to be used in the current file, C# allows accessing classes across files without explicit import statements as long as they share the same namespace.
Example of calling a class from another file, without any import statements
-
-
Main
method details- Entry point for C# application. Can be defined in a class or struct.
- Can have any access modifier(
public
,private
,protected
,internal
,protected internal
,private protected
) exceptfile
. - Must be static.
- Return type must be
void
,int
,Task
,Task<int>
. - Only one class can define
Main
method. In case multiple classes defineMain
, the program needs to be compiled with StartupObject option to specify whichMain
method to use as the entry point. docs
Specifying args in
Main
-
string[] args
is 0-indexed and does not include the file name of the program. -
Named argument example using
string[] args
-
Environment.GetCommandLineArgs()
is an alternative. This includes the file name of the program at 0-index as well. -
Do not parse args manually. Use
System.CommandLine
. github
Providing a return statement to
Main
. This is used to communicate if the program executed successfully.-
In Windows, the return value of
Main
is stored in an environment variable, which can be retrieved usingERRORLEVEL
from.bat
file or$LastExitCode
from.ps1
file.Create Powershell file to execute the program
Execute powershell file
-
In Linux,
$?
holds the value of the exit code of the last executed command. And the equivalent to the powershell file is
-
Top-level statements. An alternative to
Main
. Do not use this.The above is equivalent to
General structure of C# Program
Type system
In addition to the compiler checking type safety at compile time, the compiler embeds the type information into the executable file as metadata. The common language runtime (CLR) uses that metadata at runtime to further gurantee type safety when it allocates and reclaims memory.
All types derive from the base type System.Object
. This unified type hierarchy is called Common Type System (CTS). In CTS a type can be either a value type or reference type.
- All built-in types are
struct
and are called value types.- Value types are sealed, meaning you can’t derive a type from any value type.
- The memory is allocated inline in whatever context the variable is declared, and there is no separate heap allocation or garbage collection overhead.
- Value types are of two types
struct
andenum
. struct
is used to create custom value types.
- Types defined using
class
orrecord
are reference types.- The default value at initalization is
null
. - When object is created, the memory is allocated on the managed heap, and the variable holds reference to the location of the object.
- In the garbage collection process, there is overhead for both allocation and deallocation.
- The default value at initalization is
- Literal values. For example
4.56
. These automatically receive a type from the compiler, which inherits fromSystem.Object
as well.- The type can be specified to the compiler using
4.56f
.
- The type can be specified to the compiler using
- Generic types. For example
System.Collections.Generic.List<T>
.T
here is the placeholder for the actual type (the concrete type). var
can be used to provide an implicit type. The variable would receive the type at compile time.- Add
?
at the end of type (nullable value types) to allow the value to benull
.- Inherit from
System.Nullable<T>
.
- Inherit from
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
.
In some situations, compile-time and run-time types can be different.
In the above example, the run-time type is string
but the compile-time type is object
and IEnumerable<char>
.
- Compile-time type determines all the actions taken by the compiler, like method call resolution, overload resolution, and available implicit and explicit casts.
- Run-time type determines all actions that are resolved at run time, like dispatching virtual method calls, evaluating
is
andswitch
expressions, and other type testing APIs.
Custom types
How to choose which one to use
- If the data storage size is small, no more than 64 bytes, choose
struct
orrecord struct
. - If the type is immutable, or you want nondestructive mutation (create a new instance of object with modified values, instead of directly modifying the original instance), choose
struct
orrecord struct
. - If your type should have value semantics for equality (two instances are considered equal if their values are the same), choose
record class
orrecord struct
. - If the type is primarily used for storing data, not behavior, choose a
record class
orrecord struct
. - If the type is part of an inheritance hierarchy, choose a
record class
orclass
. - If the type uses polymorphism, choose a
class
. - If the primary purpose if behavior, choose a
class
.
Summary of above
- Classes typpically store data that is intended to be modifed after a class object is created.
- Structs are best suited for small data structures, that typically would not be modifed after the struct is created.
- Records typpically store data that isn’t instended to be modified after object is created. And they are mostly used for checking value equality, since records modify
Equals
andGetHashCode
, and two records are equal if all their properties have the same values.
Namespaces
Namespaces used to organize classes. System
is namespace and Console
is class inside that namespace.
To avoid writing the namespace use using
global
is the “root” namespace. This is set by default by .NET depending on the application.
Classes
- When a variable is declared with class, the default value is
null
until an onject is assigned. - When the object is created, enough memory is allocated on the managed heap for that specific object, which is later reclaimed by garbage collector.
Class defition [access modifier] - [class] - [identifier]
. Example public class Customer
access modifier
- default isinternal
.- all the fields, properties, methods and events defined in the class are collectively referred to as class members.
To create an object from the class use new
. Example Customer object1 = new Customer()
. This syntax can be simplified to Customer object1 = new ()
;
Use field initializer to set the default value of the fields in the class. And then in the constructor, you can provide an initial value as well.
In the above example, since the constructor is only being used to set the values for the fields, you can use a primary constructor instead.
Inheritance example public class Manager : Employee { }
.
Records
Use records when
- Object is immutable. Useful to make a type thread-safe, or you want the objects to have the same hash code in a hash table..
- You are interested in checking equality (all property and fields values compare equal) between two objects.
Do not use record types, when you want to check for only one instance of a class. In that case, you need to do reference equality.
At compile time will create method for equality, ToString
and Deconstruct
(if positional parameters were used).
Syntax
- Instead of
class
userecord
. - Instead of
struct
userecord struct
.
Example