# Mastering C# Part 2.3 - Fundamentals

# Enumeration

In C#, an **enumeration (enum)** is a special value type that allows you to define a set of named constants. Enums are used to represent values that are logically related, making the code easier to read, maintain, and understand.

Here’s a step-by-step guide to understanding and working with enumerations in C#:

---

### 1\. **Defining an Enum**

You define an enum using the `enum` keyword, followed by the name of the enum and a set of named constants enclosed in curly braces `{}`.

**Syntax:**

```csharp
enum EnumName
{
    Constant1,  // Automatically assigned value 0
    Constant2,  // Automatically assigned value 1
    Constant3   // Automatically assigned value 2
}
```

**Example:**

```csharp
enum Day
{
    Sunday,    // 0
    Monday,    // 1
    Tuesday,   // 2
    Wednesday, // 3
    Thursday,  // 4
    Friday,    // 5
    Saturday   // 6
}
```

By default, the first value in an enum starts at `0`, and each subsequent value increments by `1`. You can change the default values if needed (explained below).

---

### 2\. **Using Enums**

Once you define an enum, you can use it in your code like any other type, such as `int`, `string`, etc.

**Example:**

```csharp
Day today = Day.Monday;
Console.WriteLine(today);  // Output: Monday
```

You can also use enums in conditional statements:

```csharp
if (today == Day.Monday)
{
    Console.WriteLine("It's the start of the week!");
}
```

---

### 3\. **Assigning Custom Values to Enum Members**

By default, enum members are assigned consecutive integer values starting from `0`. However, you can assign specific values to the members.

**Syntax:**

```csharp
enum EnumName
{
    Member1 = value1,
    Member2 = value2,
    Member3 = value3
}
```

**Example:**

```csharp
enum Day
{
    Sunday = 1,    // 1
    Monday = 2,    // 2
    Tuesday = 3,   // 3
    Wednesday = 4, // 4
    Thursday = 5,  // 5
    Friday = 6,    // 6
    Saturday = 7   // 7
}
```

Now, each day of the week has a custom assigned value.

---

### 4\. **Enums with Different Base Types**

By default, the underlying type of an enum is `int`. However, you can specify a different base type such as `byte`, `short`, `long`, etc.

**Syntax:**

```csharp
enum EnumName : BaseType
{
    Member1 = value1,
    Member2 = value2
}
```

**Example:**

```csharp
enum Day : byte
{
    Sunday = 1,
    Monday = 2,
    Tuesday = 3,
    Wednesday = 4,
    Thursday = 5,
    Friday = 6,
    Saturday = 7
}
```

Here, we are using `byte` as the base type for the `Day` enum, which uses less memory than the default `int`.

---

### 5\. **Enum Methods**

C# provides some useful methods to work with enums:

* `Enum.GetValues()`: Gets an array of all values in the enum.
    
* `Enum.GetName()`: Gets the name of an enum member by its value.
    
* `Enum.IsDefined()`: Checks whether a value is defined in the enum.
    

**Example:**

```csharp
foreach (Day day in Enum.GetValues(typeof(Day)))
{
    Console.WriteLine(day); // Prints all the days
}

int dayValue = 3;
Console.WriteLine(Enum.GetName(typeof(Day), dayValue)); // Output: Wednesday
Console.WriteLine(Enum.IsDefined(typeof(Day), 5)); // Output: True
```

---

### 6\. **Converting Enums to and from Integers**

You can easily convert between enum members and their corresponding integer values.

**Example:**

```csharp
// Enum to Integer
Day day = Day.Monday;
int dayValue = (int)day;
Console.WriteLine(dayValue);  // Output: 2

// Integer to Enum
int value = 3;
Day dayFromValue = (Day)value;
Console.WriteLine(dayFromValue);  // Output: Wednesday
```

---

### 7\. **Flags Enum (Bit Flags)**

Enums can also be used to represent bitwise flags, which allows you to combine multiple values using bitwise operations. This is useful when you want to represent multiple options in a single variable.

To define a flag enum, you use the `[Flags]` attribute and assign values that are powers of 2.

**Example:**

```csharp
[Flags]
enum Permissions
{
    None = 0,
    Read = 1,
    Write = 2,
    Execute = 4,
    Admin = Read | Write | Execute // Combine Read, Write, and Execute
}

Permissions userPermissions = Permissions.Read | Permissions.Write;
Console.WriteLine(userPermissions);  // Output: Read, Write
```

You can check for specific permissions using bitwise operations:

```csharp
if ((userPermissions & Permissions.Read) == Permissions.Read)
{
    Console.WriteLine("User has read permission.");
}
```

---

### 8\. **Enum Best Practices**

* **Use meaningful names**: Enum names should clearly represent the concept they are modeling.
    
* **Avoid magic numbers**: Don’t rely on hardcoded integer values; use enums instead.
    
* **Use** `Flags` for bit operations: Use the `[Flags]` attribute when you are representing a set of options that can be combined.
    

# Structure

In C#, a **structure** (or **struct**) is a value type that allows you to define a custom data type consisting of related variables. Structures are similar to classes but with some key differences, such as their default behavior as value types instead of reference types.

---

### 1\. **Defining a Structure**

You define a structure using the `struct` keyword, followed by the name of the structure and its members (fields, properties, methods, etc.).

**Syntax:**

```csharp
struct StructName
{
    // Fields
    public int Field1;
    public string Field2;

    // Constructor
    public StructName(int field1, string field2)
    {
        Field1 = field1;
        Field2 = field2;
    }

    // Method
    public void DisplayInfo()
    {
        Console.WriteLine($"Field1: {Field1}, Field2: {Field2}");
    }
}
```

**Example:**

```csharp
struct Person
{
    public string Name;
    public int Age;

    // Constructor to initialize the fields
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    // Method to display person details
    public void DisplayInfo()
    {
        Console.WriteLine($"Name: {Name}, Age: {Age}");
    }
}
```

---

### 2\. **Using Structures**

Once you've defined a structure, you can create instances of it and access its members.

**Example:**

```csharp
class Program
{
    static void Main()
    {
        // Creating an instance of the struct
        Person person1 = new Person("John Doe", 30);
        person1.DisplayInfo(); // Output: Name: John Doe, Age: 30

        // Accessing individual fields
        Console.WriteLine(person1.Name); // Output: John Doe
    }
}
```

---

### 3\. **Default Values in Structures**

Unlike classes, a structure cannot have a default constructor, but it automatically provides a parameterless constructor that initializes all fields to their default values (like `0` for numeric types, `null` for reference types, and `false` for booleans).

**Example:**

```csharp
struct Point
{
    public int X;
    public int Y;
}

Point point = new Point(); // X and Y are initialized to 0 by default
Console.WriteLine($"X: {point.X}, Y: {point.Y}"); // Output: X: 0, Y: 0
```

---

### 4\. **Passing Structures by Value**

Structures are **value types**, which means they are passed by value when used as method parameters. When you pass a struct to a method, the method gets a copy of the struct, not the original.

**Example:**

```csharp
struct Point
{
    public int X;
    public int Y;
}

class Program
{
    static void ModifyPoint(Point point)
    {
        point.X = 10;
        point.Y = 20;
    }

    static void Main()
    {
        Point p1 = new Point { X = 5, Y = 5 };
        ModifyPoint(p1);
        Console.WriteLine($"X: {p1.X}, Y: {p1.Y}"); // Output: X: 5, Y: 5 (unchanged)
    }
}
```

Since `Point` is a value type, `p1` was passed by value to `ModifyPoint`, meaning the method worked on a copy of the struct, not the original.

---

### 5\. **Passing Structures by Reference**

To modify the original struct inside a method, you need to pass it by reference using the `ref` or `out` keyword.

**Example using** `ref`:

```csharp
struct Point
{
    public int X;
    public int Y;
}

class Program
{
    static void ModifyPoint(ref Point point)
    {
        point.X = 10;
        point.Y = 20;
    }

    static void Main()
    {
        Point p1 = new Point { X = 5, Y = 5 };
        ModifyPoint(ref p1);
        Console.WriteLine($"X: {p1.X}, Y: {p1.Y}"); // Output: X: 10, Y: 20 (modified)
    }
}
```

Now, the original `p1` struct is modified because it was passed by reference.

---

### 6\. **Difference Between Structs and Classes**

While both structs and classes are used to define custom data types, there are key differences between them:

| Feature | Struct | Class |
| --- | --- | --- |
| Type | Value type | Reference type |
| Default constructor | Cannot define a default constructor | Can define a default constructor |
| Memory allocation | Stored on the stack | Stored on the heap |
| Nullability | Cannot be null | Can be null |
| Inheritance | Cannot inherit from other structs or classes | Can inherit from other classes |

---

### 7\. **When to Use a Struct**

* **Small, simple data types**: Structures are ideal for representing small data structures that hold a few related values.
    
* **Performance-sensitive scenarios**: Structures are allocated on the stack and are often more efficient than classes, especially when dealing with many small, short-lived objects.
    

**Example:**

```csharp
struct Rectangle
{
    public int Width;
    public int Height;

    public int Area()
    {
        return Width * Height;
    }
}

Rectangle rect = new Rectangle { Width = 5, Height = 10 };
Console.WriteLine($"Area: {rect.Area()}");  // Output: Area: 50
```

---

### 8\. **Common Structure Pitfalls**

* **Immutability**: Since structs are value types, modifying a struct can sometimes lead to unexpected behavior, especially if it’s passed by value. Always remember that structs are copied when passed to methods or assigned to variables.
    
* **Large structs**: Large structs can have a negative performance impact, especially if they are passed by value. In this case, using a class might be more efficient.
    

---

### Conclusion

In summary, structures in C# are a great way to define simple data types that hold related values. They offer performance advantages for small objects due to their stack allocation but come with limitations like immutability by default and the lack of inheritance. You should use structs when you need a lightweight, value-based type that represents small, logically grouped data.

If you have any further questions about structures or would like examples on a particular topic, feel free to ask!

# Nullable Types

In C#, **nullable types** allow value types (such as `int`, `float`, `bool`, etc.) to represent **null** in addition to their normal range of values. This is particularly useful when working with databases or situations where you need to differentiate between an "unset" or "missing" value and the default value of a type.

### 1\. **What is a Nullable Type?**

Normally, value types in C# (like `int`, `float`, `double`, `bool`, etc.) cannot be null. However, sometimes you need to represent a scenario where the value is not provided, or it is "undefined." For this, C# provides nullable types.

A nullable type is a special version of a value type that can also represent `null`. You can define a nullable type using the `?` modifier after the type.

**Syntax:**

```csharp
Type? variableName;
```

---

### 2\. **Declaring Nullable Types**

You can declare a nullable type by appending a `?` to a value type.

**Example:**

```csharp
int? myNullableInt = null; // Nullable int, can hold null or an integer value
bool? myNullableBool = null; // Nullable boolean, can hold null or true/false
DateTime? myNullableDate = null; // Nullable DateTime, can hold null or a DateTime value
```

Here, `myNullableInt`, `myNullableBool`, and `myNullableDate` can store `null` values in addition to their normal data types.

---

### 3\. **Working with Nullable Types**

You can assign `null` to a nullable type just like any other reference type, but when working with nullable types, you need to check whether they have a value or are null before accessing the value.

### a. **Checking for** `null`

You can check if a nullable type has a value by using the `HasValue` property or by comparing it directly to `null`.

**Example:**

```csharp
int? myNullableInt = 10;
if (myNullableInt.HasValue)
{
    Console.WriteLine($"Value: {myNullableInt.Value}");
}
else
{
    Console.WriteLine("No value assigned");
}

// Alternatively
if (myNullableInt != null)
{
    Console.WriteLine($"Value: {myNullableInt.Value}");
}
```

### b. **Accessing the Value**

To get the value of a nullable type, you can use the `Value` property. However, this will throw an exception if the nullable type is `null`. It’s safer to check `HasValue` first, or use the null-coalescing operator (`??`).

**Example:**

```csharp
int? myNullableInt = 10;
int value = myNullableInt ?? -1;  // If myNullableInt is null, -1 will be used
Console.WriteLine(value);  // Output: 10
```

If `myNullableInt` is `null`, the null-coalescing operator (`??`) provides a default value (`-1` in this case).

---

### 4\. **Null-Coalescing Operator (**`??`)

The null-coalescing operator (`??`) is used to provide a default value when a nullable type is `null`.

**Example:**

```csharp
int? myNullableInt = null;
int value = myNullableInt ?? 100;  // If myNullableInt is null, use 100
Console.WriteLine(value);  // Output: 100
```

In this case, `myNullableInt` is `null`, so the operator returns the default value `100`.

---

### 5\. **Null Conditional Operator (**`?.`)

The **null-conditional operator** (`?.`) allows you to safely access members of an object or value types without having to explicitly check for `null`. It returns `null` if the value is `null` rather than throwing an exception.

**Example:**

```csharp
int? myNullableInt = null;
int? result = myNullableInt?.ToString().Length; // Will be null if myNullableInt is null
Console.WriteLine(result);  // Output: null
```

Here, `myNullableInt` is `null`, so the `?.` operator avoids a `NullReferenceException`.

---

### 6\. **Nullable Value Type Default Behavior**

By default, nullable types are initialized to `null` when declared.

**Example:**

```csharp
int? myNullableInt;  // Default is null
Console.WriteLine(myNullableInt == null);  // Output: True
```

This is in contrast to regular value types (like `int`), which are initialized to their default value (`0` for `int`, `false` for `bool`, etc.) when declared.

---

### 7\. **Converting Between Nullable and Non-Nullable Types**

You can assign a non-nullable value to a nullable type, but the reverse operation requires checking for `null`.

**Example of nullable to non-nullable assignment:**

```csharp
int? myNullableInt = 20;
int myInt = myNullableInt ?? 0;  // If null, 0 will be used
Console.WriteLine(myInt);  // Output: 20
```

**Example of non-nullable to nullable assignment:**

```csharp
int normalInt = 25;
int? myNullableInt = normalInt;  // This works because normalInt is not null
Console.WriteLine(myNullableInt);  // Output: 25
```

---

### 8\. **Nullable Types with Collections**

Nullable types are useful when working with collections (like `List<T>`) where elements can be missing or optional.

**Example with** `List<int?>`:

```csharp
List<int?> numbers = new List<int?> { 1, 2, null, 4 };

foreach (var number in numbers)
{
    if (number.HasValue)
    {
        Console.WriteLine(number.Value);  // Output: 1, 2, 4
    }
    else
    {
        Console.WriteLine("Null value");  // Output: Null value
    }
}
```

---

### 9\. **Nullable Types with Database Interactions**

Nullable types are especially useful when working with databases (e.g., when dealing with `null` values in SQL columns). For example, a `NULL` value in a database column can be represented by a `nullable` value type in C#.

**Example with Entity Framework:**

```csharp
public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int? DepartmentId { get; set; }  // Nullable DepartmentId
}
```

In this case, `DepartmentId` can be `null`, indicating that the employee does not belong to any department.

---

### Conclusion

Nullable types in C# are a powerful feature, allowing value types to represent `null` in addition to their usual values. This is particularly useful in scenarios where data can be optional or missing, such as working with databases or dealing with uninitialized variables. You can use nullable types with ease by leveraging features like the `HasValue` property, `Value` property, and null-coalescing operator (`??`) to handle `null` values safely.

# Control Flow and Exception Handling

Welcome to this tutorial on control transfer and exception handling in C#. In this lesson, we'll explore two important concepts in C# programming: the `goto` statement and the `throw` statement. We'll also show how to combine them with loops like `for` and `foreach` to enhance your understanding.

### **1\. The** `goto` Statement

The `goto` statement is used to transfer control to a labeled statement within the same method. This can be useful for skipping parts of a program or jumping to specific blocks of code.

### **Example: Using** `goto` in a `switch` Statement

In this example, we use `goto` to transfer control from one case to another in a `switch` statement.

* * ```csharp
        using System;
        
        namespace Data_Types
        {
            class DataTypes
            {
                static void Main(string[] args)
                {
                    int num = 5;
        
                    switch (num)
                    {
                        case 1:
                        case 2:
                            Console.WriteLine("The number is 1");
                            break;
        
                        case 3:
                        case 4:
                            Console.WriteLine("The number is 3");
                            break;
        
                        case 5:
                            Console.WriteLine("I found it but let's go to case 3 and execute");
                            goto case 3; // Control is transferred to case 3
                        default:
                            Console.WriteLine("Default case");
                            break;
                    }
                }
            }
        }
        ```
        
        ### **Output:**
        
        ```csharp
        I found it but let's go to case 3 and execute
        The number is 3
        ```
        
        ### **Explanation:**
        
        In the `switch` statement, when `num` is 5, we use `goto case 3;` to transfer control to the code block where `num` is 3. This allows us to skip the lines in between and reuse the code for a similar case.
        
        ### **2\. The** `throw` Statement
        
        The `throw` statement is used to manually raise exceptions in C#. You create an exception object (typically derived from the `Exception` class) and throw it using the `throw` keyword.
        
        ### **Example: Using** `throw` to Handle Exceptions
        
        In this example, we'll create a `NullReferenceException` manually using the `throw` statement. We'll handle the exception using `try-catch`.
        
        ```csharp
        using System;
        
        namespace Data_Types
        {
            class ThrowStatements
            {
                // Taking null in the string
                static string sub = null;
        
                static void displaySubject(string sub1)
                {
                    // Check if the string is null and throw an exception
                    if (sub1 == null)
                    {
                        throw new NullReferenceException("Subject cannot be null");
                    }
                }
        
                static void Main(string[] args)
                {
                    try
                    {
                        displaySubject(sub); // Passing null to trigger exception
                    }
                    catch (Exception ex)
                    {
                        // Catching the exception and displaying the message
                        Console.WriteLine(ex.Message); // "Subject cannot be null"
                    }
                }
            }
        }
        ```
        
        ### **Output:**
        
        ```csharp
        Subject cannot be null
        ```
        
        ### **Explanation:**
        
        In the method `displaySubject`, we check if the `sub1` parameter is `null`. If it is, we throw a `NullReferenceException`. The `try-catch` block in the `Main` method catches the exception and prints the exception message.
        
        ### **3\. Combining Loops and Exception Handling**
        
        Now, let's combine loops and exception handling to create a more comprehensive example. We'll use `foreach` to loop through an array of numbers and simulate some errors using the `throw` statement.
        
        ```csharp
        using System;
        
        namespace Data_Types
        {
            class Program
            {
                static void Main(string[] args)
                {
                    int[] numbers = { 1, 2, 3, -1, 4, -5, 6 };
        
                    foreach (int num in numbers)
                    {
                        try
                        {
                            Console.WriteLine($"Processing number: {num}");
        
                            // Simulate an error if the number is negative
                            if (num < 0)
                            {
                                throw new ArgumentException("Negative number detected");
                            }
        
                            // Simulate successful processing
                            Console.WriteLine($"Successfully processed: {num}");
                        }
                        catch (ArgumentException ex)
                        {
                            // Catch and handle the exception
                            Console.WriteLine($"Error: {ex.Message}");
                        }
                    }
                }
            }
        }
        ```
        
        ### **Output:**
        
        ```csharp
        Processing number: 1
        Successfully processed: 1
        Processing number: 2
        Successfully processed: 2
        Processing number: 3
        Successfully processed: 3
        Processing number: -1
        Error: Negative number detected
        Processing number: 4
        Successfully processed: 4
        Processing number: -5
        Error: Negative number detected
        Processing number: 6
        Successfully processed: 6
        ```
        
        ### **Explanation:**
        
        In this example:
        
        * We use a `foreach` loop to process each number in the `numbers` array.
            
        * If the number is negative, we throw an `ArgumentException`.
            
        * The `try-catch` block ensures that the exception is handled gracefully, allowing the program to continue processing other numbers.
            
        
        ### **4\. A Practical Example: Using** `goto` and `throw` with Loops
        
        Let's create a more complex scenario where we use both `goto` and `throw` together. This example simulates a simple menu system where we can process different commands. If an invalid command is entered, the program throws an exception, and we use `goto` to jump back to the main menu.
        
        ```csharp
        using System;
        
        namespace Data_Types
        {
            class Program
            {
                static void Main(string[] args)
                {
                    string command;
                    bool continueRunning = true;
        
                    while (continueRunning)
                    {
                        Console.WriteLine("Enter a command (start, stop, quit):");
                        command = Console.ReadLine();
        
                        try
                        {
                            switch (command.ToLower())
                            {
                                case "start":
                                    Console.WriteLine("Starting the process...");
                                    break;
                                case "stop":
                                    Console.WriteLine("Stopping the process...");
                                    break;
                                case "quit":
                                    Console.WriteLine("Exiting the program.");
                                    continueRunning = false;
                                    break;
                                default:
                                    throw new InvalidOperationException("Invalid command entered.");
                            }
                        }
                        catch (InvalidOperationException ex)
                        {
                            Console.WriteLine(ex.Message);
                            goto MainMenu; // Jump back to the main menu
                        }
        
                    MainMenu: ;
                    }
                }
            }
        }
        ```
        
        ### **Output:**
        
        ```csharp
        Enter a command (start, stop, quit):
        foo
        Invalid command entered.
        Enter a command (start, stop, quit):
        quit
        Exiting the program.
        ```
        
        ### **Explanation:**
        
        * The program continuously prompts the user for a command.
            
        * If the user enters an invalid command, an exception is thrown and caught.
            
        * Using the `goto MainMenu;` statement, the program jumps back to the prompt for the command.
            
        
        ### **Conclusion**
        
        In this tutorial, we learned how to use the `goto` and `throw` statements in C#:
        
        1. `goto`: Used to transfer control to a labeled statement within the same method.
            
        2. `throw`: Used to throw exceptions manually, enabling error handling with `try-catch` blocks.
            
        3. **Combining Loops and Exception Handling**: We saw how to combine loops like `foreach` with exception handling for more robust programs.
