Value types are defined using the struct keyword instead of class. In most aspects, structures are identical to classes and the characteristics presented in this chapter for classes apply to structures too. However, there are several key differences:
Structures do not support inheritance. Although a structure can implement any number of interfaces, it cannot derive from another structure. For this reason, structure members cannot have the protected access modifier. Also, a structured method or property cannot be abstract or virtual.
A structure cannot declare a default (parameterless) constructor.
Structures can be instantiated without using the new operator.
In a structure declaration, fields cannot be initialized unless they are declared const or static.
Enumerations
enum Priority:byte
{
Low = 10,
Normal,//11
Important = 20,
Urgent//21
}
Priority p = Priority.Normal;
int i = (int)Priority.Normal;
Enum.TryParse("Normal", out Priority p); // p is Normal
Enum.TryParse(typeof(Priority), "normal", true, out object o);
Priority p = (Priority)o; // p is Normal
There is an implicit namespace that is the root of all namespaces (and contains all namespaces and types that are not declared in a named namespace). This namespace is called global. If you need to include it to specify a fully qualified name, then you must separate it with :: and not with a dot, as in global::System.String. This can be necessary in situations where namespace names collide. Here is an example:
class Employee
{
public const int StartId = 100;
public readonly int EmployeeId;
public string FirstName;
public string LastName;
public Employee(int id)
{
EmployeeId = id;
}
}
Employee obj = new Employee(1);
obj.FirstName = "John";
obj.LastName = "Doe";
Methods
class Employee
{
public int EmployeeId;
public string FirstName;
public string LastName;
public string GetEmployeeName()
{
return $"{FirstName} {LastName}";
}
public string GetEmployeeName() => $"{FirstName} {LastName}";
}
An access modifier: This specifies the visibility of the method. This is optional and private by default.
Modifiers such as virtual, abstract, sealed, or static
A return type: This could be void if the method does not return any value.
A name: This must be a valid identifier.
Zero, one, or more parameters: These are specified with a type, name, and optionally, the ref, in, or out specifier.
class Employee
{
public int EmployeeId;
public string FirstName;
public string LastName;
public Employee(int employeeId,
string firstName, string lastName)
{
EmployeeId = employeeId;
FirstName = firstName;
LastName = lastName;
}
public string GetEmployeeName() =>
$"{FirstName} {LastName}";
}
Employee obj = new Employee(1, "John", "Doe");
//Considering the Employee class without a user-defined constructor
Employee obj = new Employee()
{
EmployeeId = 1,
FirstName = "John",
LastName = "Doe"
};
Properties
class Employee
{
private int employeeId;
private string firstName;
private string lastName;
public int EmployeeId
{
get { return employeeId; }
set { employeeId = value; }
}
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
public string val
{
get{}
private set {}
}
}
class Employee
{
public int EmployeeId { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public Employee(int id, string firstName, string lastName)
{
EmployeeId = id;
FirstName = firstName;
LastName = lastName;
}
}
class Employee
{
public int EmployeeId { get; set; } = 1;//initialized with 1
public string FirstName { get; set; }
public string LastName { get; set; }
}
Indexers
class ProjectRoles
{
readonly Dictionary<int, string> roles =
new Dictionary<int, string>();
public string this[int projectId]
{
get
{
if (!roles.TryGetValue(projectId, out string role))
throw new Exception("Project ID not found!");
return role;
}
set
{
roles[projectId] = value;
}
}
}
this keyword
class Employee
{
public int EmployeeID;
public string FirstName;
public string LastName;
public Employee(int EmployeeID,
string FirstName, string LastName)
{
this.EmployeeID = EmployeeID;
this.FirstName = FirstName;
this.LastName = LastName;
}
}
Static members
class Employee
{
private static int id = 1;
public int EmployeeId { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
private Employee(int id, string firstName, string lastName)
{
EmployeeId = id;
FirstName = firstName;
LastName = lastName;
}
public static Employee Create(string firstName,
string lastName)
{
return new Employee(id++, firstName, lastName);
}
}
Static classes
static class cannot be instantiated. Since we cannot create instances of a static class
static class MassConverters
{
public static double PoundToKg(double pounds)
{
return pounds * 0.45359237;
}
public static double KgToPound(double kgs)
{
return kgs * 2.20462262185;
}
}
var lbs = MassConverters.KgToPound(42.5);
var kgs = MassConverters.PoundToKg(180);
static constructor
In a static class when the first static member of the class is accessed for the first time
In a non-static class when the class is instantiated for the first time
ref, in, and out parameters
ref keyword allows us to create a call-by-reference mechanism rather than a call-by-value mechanism. A ref keyword is specified when we declare and invoke the method.
class Program
{
static void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
static void Main(string[] args)
{
int num1 = 10;
int num2 = 20;
Console.WriteLine($"Before swapping: num1={num1}, num2={num2}");
Swap(ref num1, ref num2);
Console.WriteLine($"After swapping: num1={num1}, num2={num2}");
}
}
class Project
{
Employee owner;
public string Name { get; private set; }
public Project(string name, Employee owner)
{
Name = name;
this.owner = owner;
}
public ref Employee GetOwner()
{
return ref owner;
}
public override string ToString() =>
$"{Name} (Owner={owner.FirstName} {owner.LastName})";
}
It is not possible to return a reference to a local variable.
It is not possible to return a reference to this.
It is possible to return references to class fields but also to properties without a set accessor.
It is possible to return a reference to ref/in/out parameters.
Returning by reference breaks the encapsulation because the caller gets full access to the state, or parts of the state, of an object.
The in keyword is very similar to the ref keyword. It causes an argument to be passed by reference. However, the key difference is that an in argument cannot be modified by the called method. An in parameter is basically a readonly ref parameter.
static void DontTouch(in int value, in string text)
{
value = 42; // error
++value; // error
text = null; // error
}
int i = 0;
string s = "hello";
DontTouch(i, s);
The out keyword is similar to the ref keyword. The difference is that a variable passed as an out argument does not have to be initialized before the method called, but the method taking an out parameter must assign a value to it before returning.
Methods with a variable number of arguments
static bool Any(params bool [] values)
{
foreach (bool v in values)
if (v) return true;
return false;
}
static bool All(params bool[] values)
{
if (values.Length == 0) return false;
foreach (bool v in values)
if (!v) return false;
return true;
}
Named arguments
struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x = 0, int y = 0)
{
X = x;
Y = y;
}
}
Point p1 = new Point(x: 1, y: 2); // x = 1, y = 2
Point p2 = new Point(1, y: 2); // x = 1, y = 2
Point p3 = new Point(x: 1, 2); // x = 1, y = 2
Point p4 = new Point(y: 2); // x = 0, y = 2
Point p5 = new Point(x: 1); // x = 1, y = 0
Access modifiers
public: A public field can be accessed by any part of the code in the same assembly or in another assembly.
protected: A protected type or member can be accessed only in the current class and in a derived class.
internal: An internal type or member is accessible only within the current assembly.
protected internal: This is a combination of protected and internal access levels. A protected internal type or member is accessible in the current assembly or in a derived class.
private: A private type or member can be accessed only inside the class or struct. This is the least-accessible level defined in C#.
private protected: This is a combination of private and protected access levels. A private protected type or type member is accessible by code in the same class, or in a derived class, but only within the same assembly.
Partial classes
partial class Employee
{
partial void Promote();
}
partial class Employee
{
public int EmployeeId { get; set; }
}
partial class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
partial void Promote()
{
Console.WriteLine("Employee promoted!");
}
}
switch (expression)
{
case value1:
statement 1;
break;
case value2:
statement 2;
statement 3;
break;
default:
statement 4;
break;
}
try,catch,finally
try
{
Console.WriteLine("nice started");
int l = 12;
throw new Exception("there are problem");
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
finally // called at the end and don't care if is their exception or not
{
Console.WriteLine("nice finished");
}
foreach
string[] languages = { "Java", "C#", "Python", "C++", "JavaScript" };
foreach (string lang in languages)
{
Console.WriteLine(lang);
}
Span<int> arr = stackalloc int[]{ 1, 1, 2, 3, 5, 8 };
foreach(ref int n in arr)
{
n *= 2;
}
foreach(ref readonly var n in arr)
{
Console.WriteLine(n);
}
goto
for (int i = 0; i <= 10; i++)
{
Console.WriteLine(i);
if (i == 5)
{
goto printmessage;
}
}
printmessage:
Console.WriteLine("The goto statement is executed");
yield return and yield break
IEnumerable<int> GetNumbers()
{
for (int i = 1; i <= 100; ++i)
{
Thread.Sleep(1000);
Console.WriteLine($"Produced: {i}");
yield return i;
}
}
foreach(var i in GetNumbers().Take(5))
{
Console.WriteLine($"Consumed: {i}");
}
disposing with using block
using System;
class DisposableDemoClass : IDisposable
{
public void Dispose()
{
Console.WriteLine("Dispose called!");
}
}
class Hello
{
static void Main()
{
using(var disp = new DisposableDemoClass())
{
}
}
}
data types are provided in the System namespace. All of them, however, have a C# alias. These aliases are keywords in the C# language
The integral types
The default value of all integral types is 0. All of these types define two constants called MinValue and MaxValue, which provide the minimum and maximum value of the type.
Integral literals, which are numbers that appear directly in code (such as 0, -42, and so on), can be specified as decimal, hexadecimal, or binary literals. Decimal literals do not require any suffix. Hexadecimal literals are prefixed with 0x or 0X, and binary literals are prefixed with 0b or 0B. An underscore (_) can be used as a digit separator with all numeric literals.
int dec = 32;
int hex = 0x2A;
int bin = 0b_0010_1010;
An integral value without any suffix is inferred by the compiler as int. To indicate a long integer, use l or L for a signed 64-bit integer and ul or UL for an unsigned 64-bit integer.
The floating-point types
The default value for floating-point types is 0. These types also define two constants called MinValue and MaxValue that provide the minimum and maximum value of the type. However, these types also provide constants that represent not-a-number (System.Double.NaN) and infinity (System.Double.NegativeInfinity and System.Double.PositiveInfinity)
var a = 42.99;//double
float b = 19.50f;
System.Double c = -1.23;
The decimal type
The decimal type can represent up to 28 decimal places or 128 bits. is important to note that the decimal type minimizes errors during rounding but does not eliminate the need for rounding. For instance, the result of the operation 1m / 3 * 3 is not 1 but 0.9999999999999999999999999999. On the other hand, Math.Round(1m / 3 * 3) yields the value 1.
decimal a = 42.99m;
var b = 12.45m;
System.Decimal c = 100.75M;
The char type
16 bits
char a = 'A';
char b = '\x0065';
char c = '\u15FE';
The string type
A string is an array of characters. In C#, the type for representing a string is called string and is an alias for the .NET System.String. You can use any of these two types interchangeably. Internally, a string contains a read-only collection of char objects. This makes strings immutable, which means that you cannot change a string but need to create a new one every time you want to modify the content of an existing string. Strings are not null-terminated (unlike other languages such as C++) and can contain any number of null characters ('\0').
string s1; // unitialized
string s2 = null; // initialized with null
string s3 = String.Empty; // empty string
string s4 = "hello world"; // initialized with text
var s5 = "hello world";
System.String s6 = "hello world";
char[] letters = { 'h', 'e', 'l', 'l', 'o'};
string s7 = new string(letters); // from an array of chars
char c = s4[0]; // OK
s4[0] = 'H'; // error
var s8 = s6.Remove(5); // hello
var s9 = s6.ToUpper(); // HELLO WORLD
formats
int i = 42;
string s1 = "This is item " + i.ToString();
string s2 = string.Format("This is item {0}", i);
string s3 = $"This is item {i}";
string s4 = @"\b\w+\b";
The object type
The object type is the base type for all other types in C#, even though you do not specify this explicitly, as we will see in the following chapters. The object keyword in C# is an alias for the .NET System.Object type. You can use these two interchangeably.
GetType() method, which is not virtual and which returns a System.Type object with information about the type of the current instance.
Equals() behavior is different for reference and value types. for reference types, this method performs reference equality; this means it checks whether the two variables point to the same object on the heap. For value types, it performs value equality; this means that the two variables are of the same type and that the public and private fields of the two objects are equal.
The default value of a variable of the object type is null
Reference types and value types
value types All user-defined types declared as structures (with the struct keyword) are value types. Although all types are implicitly derived from the object, type value types do not support explicit inheritance Value types are typically stored on the stack in memory
int a = 20;
DateTime dt = new DateTime(2019, 12, 25);
int a = 20;
int b = a; // b is 20
a = 42; // a is 42, b is 20
reference type does not contain the value directly but a reference to a memory location where the actual value is stored. The built-in data types object and string are reference types. Arrays, interfaces, delegates, and any user-defined type defined as a class are also called reference types.
Nullable types
Reference types have the default value null, which indicates that a variable is not assigned to the instance of any object. Value types do not have such an option. However, there are cases when no value is a valid value for a value type too. To represent such cases, you can use a nullable type. A nullable type is an instance of System.Nullable, a generic value type that can represent the values of an underlying T type, which can only be a value type, as well as an additional null value.
Nullable<int> a;
Nullable<int> b = null;
Nullable<int> c = 42;
int? a;
int? b = null;
int? c = 42;
You can use the HasValue property to check whether a nullable type object has a value, and Value to access the underlying value:
if (c.HasValue)
Console.WriteLine(c.Value);
string? s1 = null; // OK, nullable type
string s2 = null; // error, non-nullable type
You assign values to a nullable type object the same way you would assign to the underlying type.
You can use the GetValueOrDefault() method to get either the assigned value or the default value of the underlying type if no value is assigned.
Boxing is performed on the underlying type. If the nullable type object has not assigned any value, the result of boxing is a null object.
You can use the null-coalescing operator, ??, to access the value of the object of a nullable type (for example, int d = c ?? -1;).
int[][] arr1;
int[][] arr2 = null;
int[][] arr3 = new int[2][];
arr3[0] = new int[3];
arr3[1] = new int[] { 1, 1, 2, 3, 5, 8 };
int[][] arr4 = new int[][]
{
new int[] { 1, 2, 3 },
new int[] { 1, 1, 2, 3, 5, 8 }
};
int[][] arr5 =
{
new int[] { 1, 2, 3 },
new int[] { 1, 1, 2, 3, 5, 8 }
};
int[][,] arr6 = new int[][,]
{
new int[,] { { 1, 2}, { 3, 4 } },
new int[,] { {11, 12, 13}, { 14, 15, 16} }
};
Type Conversion
string s = "example";
object o = s; // implicit conversion
string r = (string)o; // explicit conversion
public readonly struct fancyint
{
private readonly int value;
public fancyint(int value)
{
this.value = value;
}
public static implicit operator int(fancyint v) => v.value;
public static explicit operator fancyint(int v) => new fancyint(v);
public override string ToString() => $"{value}";
}
fancyint a = new fancyint(42);
int i = a; // implicit conversion
fancyint b = (fancyint)i; // explicit conversion
DateTime dt1 = DateTime.Parse("2019.08.31");
DateTime.TryParse("2019.08.31", out DateTime dt2);
int i1 = int.Parse("42"); // successful, i1 = 42
int i2 = int.Parse("42.15"); // error, throws exception
int.TryParse("42.15", out int i3); // error, returns false,
// i3 = 0
Operators
The null-conditional operators
The null-conditional operator has two forms: ?. (also known as the Elvis operator) to apply member access and ?[] to apply element access for an array. These operators apply the operation to their operand if and only if that operand is not null. Otherwise, the result of applying the operator is also null
gcc and g++ are the GNU C and C++ compilers, respectively
clang and clang++ are the LLVM compilers, respectively
Use CMake to define how to invoke compilers on the source code files
gdb/lldb — debuggers
gdb is a debugger provided by GNU
lldb is a debugger provided by LLVM
Use these to detect issues when running your binaries
It generally does not matter which compiler was used to generate the binary, i.e. you can use LLDB to debug a code compiled with a GNU compiler or vice versa
diffrence between make and cmake
Make (or rather a Makefile) is a buildsystem – it drives the compiler and other build tools to build your code.
CMake is a generator of buildsystems. It can produce Makefiles, it can produce Ninja build files, it can produce KDEvelop or Xcode projects, it can produce Visual Studio solutions. From the same starting point, the same CMakeLists.txt file. So if you have a platform-independent project, CMake is a way to make it buildsystem-independent as well.
If you have Windows developers used to Visual Studio and Unix developers who swear by GNU Make, CMake is (one of) the way(s) to go.
cmake -G"MSYS Makefiles" when compiling for MinGW
cmake -G "Visual Studio 16 2019" -A Win32 when compiling for vs
cmake -G "Visual Studio 16 2019" -A x64 when compiling for vs
The GNU C and C++ compiler are called gcc and g++, respectively.
// Compile and link source file hello.c into executable a.exe (Windows) or a (Unixes)
> gcc hello.c
// (Unixes / Mac OS X) In Bash shell
$ gcc -o hello hello.c
$ chmod a+x hello
$ ./hello
// (Windows) In CMD shell
> g++ -o hello.exe hello.cpp
// Compile and link source hello.cpp into executable hello.exe
> hello
// Execute under CMD shell
// (Unixes / Mac OS X) In Bash shell
$ g++ -o hello hello.cpp
$ chmod a+x hello
$ ./hello
$ g++ -Wall -g -o Hello.exe Hello.cpp
-o: specifies the output executable filename.
-Wall: prints "all" Warning messages.
-g: generates additional symbolic debuggging information for use with gdb debugger.
// Compile-only with -c option
> g++ -c -Wall -g Hello.cpp
// Link object file(s) into an executable
> g++ -g -o Hello.exe Hello.o
-c: Compile into object file "Hello.o". By default, the object file has the same name as the source file with extension of ".o" (there is no need to specify -o option). No linking with other object files or libraries.
Linking is performed when the input file are object files ".o" (instead of source file ".cpp" or ".c"). GCC uses a separate linker program (called ld.exe) to perform the linking.
Suppose that your program has two source files: file1.cpp, file2.cpp. You could compile all of them in a single command:
g++ -o myprog.exe file1.cpp file2.cpp
However, we usually compile each of the source files separately into object file, and link them together in the later stage. In this case, changes in one file does not require re-compilation of the other files.
With Boost.Coroutine it is possible to use coroutines in C++. Coroutines are a feature of other programming languages, which often use the keyword yield for coroutines. In these programming languages, yield can be used like return. However, when yield is used, the function remembers the location, and if the function is called again, execution continues from that location.