# Mastering C# Part 2.2 - Fundamentals

Now, let’s learn about Access Modifiers in C#

# Access Modifiers

Access modifiers in C# define how accessible a member (e.g., a field, method, or class) is to other parts of the program. Here’s a detailed breakdown of each access modifier based on the table you provided:

* ---
    
    ### **1\. Public**
    
    * **Scope**: Entire program.
        
    * **Usage**: Members marked as `public` can be accessed from **anywhere** in the program or even from another assembly.
        
    * **Example**:
        
        ```csharp
        public class Example
        {
            public int PublicValue = 10; // Accessible anywhere
        }
        ```
        
    
    ---
    
    ### **2\. Private**
    
    * **Scope**: Containing class.
        
    * **Usage**: Members marked as `private` are accessible **only within the same class** where they are declared.
        
    * **Example**:
        
        ```csharp
        class Example
        {
            private int PrivateValue = 20; // Accessible only inside this class
        
            void ShowValue()
            {
                Console.WriteLine(PrivateValue); // Valid
            }
        }
        ```
        
    
    ---
    
    ### **3\. Protected**
    
    * **Scope**: Containing class and derived types.
        
    * **Usage**: Members marked as `protected` are accessible **within the same class** or in **derived classes**.
        
    * **Example**:
        
        ```csharp
        class Parent
        {
            protected int ProtectedValue = 30; // Accessible in derived classes
        }
        
        class Child : Parent
        {
            void ShowValue()
            {
                Console.WriteLine(ProtectedValue); // Valid
            }
        }
        ```
        
    
    ---
    
    ### **4\. Internal**
    
    * **Scope**: Current assembly.
        
    * **Usage**: Members marked as `internal` are accessible **within the same assembly, Derived class** but **not** from other assemblies.
        
    * **Example**:
        
        ```csharp
        internal class Example
        {
            internal int InternalValue = 40; // Accessible within the same assembly
        }
        ```
        
    
    ---
    
    ### **5\. Protected Internal**
    
    * **Scope**: Derived types or within the current assembly.
        
    * **Usage**: Members marked as `protected internal` are accessible to:
        
        * **Derived classes** (even in different assemblies).
            
        * **Any code** within the same assembly.
            
    * **Example**:
        
        ```csharp
        class Parent
        {
            protected internal int ProtectedInternalValue = 50;
        }
        
        class Child : Parent
        {
            void ShowValue()
            {
                Console.WriteLine(ProtectedInternalValue); // Valid
            }
        }
        ```
        
    
    ---
    
    ### **6\. Private Protected**
    
    * **Scope**: Derived types within the current assembly.
        
    * **Usage**: Members marked as `private protected` are accessible:
        
        * **Only in derived classes within the same assembly**.
            
        * **Not accessible outside the assembly**, even by derived classes.
            
    * **Example**:
        
        ```csharp
        class Parent
        {
            private protected int PrivateProtectedValue = 60;
        }
        
        class Child : Parent
        {
            void ShowValue()
            {
                Console.WriteLine(PrivateProtectedValue); // Valid within same assembly
            }
        }
        ```
        
    
    ---
    
    ### **Summary Table**
    
    | **Modifier** | **Entire Program** | **Containing Class** | **Current Assembly** | **Derived Types** | **Derived Types (Same Assembly)** |
    | --- | --- | --- | --- | --- | --- |
    | `public` | ✅ | ✅ | ✅ | ✅ | ✅ |
    | `protected` | ❌ | ✅ | ❌ | ✅ | ✅ |
    | `internal` | ❌ | ✅ | ✅ | ❌ | ❌ |
    | `protected internal` | ❌ | ✅ | ✅ | ✅ | ✅ |
    | `private` | ❌ | ✅ | ❌ | ❌ | ❌ |
    | `private protected` | ❌ | ✅ | ❌ | ❌ | ✅ |
    
    In C#, **assemblies** are the building blocks of .NET applications. An assembly is essentially a compiled output of your code, typically in the form of a `.dll` (Dynamic Link Library) or `.exe` (Executable).
    

**Operators | Params | Type Casting**

**Operators in C#** are essential for performing computations and logic operations on variables and values (called operands). Below is an overview of the key operator categories in C#:

---

### **1\. Arithmetic Operators**

These are used for mathematical operations:

| Operator | Description | Example |
| --- | --- | --- |
| `+` | Addition | `a + b` |
| `-` | Subtraction | `a - b` |
| `*` | Multiplication | `a * b` |
| `/` | Division | `a / b` |
| `%` | Modulus (remainder) | `a % b` |

---

### **2\. Relational Operators**

Used to compare values and return `true` or `false`:

| Operator | Description | Example |
| --- | --- | --- |
| `==` | Equal to | `a == b` |
| `!=` | Not equal to | `a != b` |
| `>` | Greater than | `a > b` |
| `<` | Less than | `a < b` |
| `>=` | Greater than or equal to | `a >= b` |
| `<=` | Less than or equal to | `a <= b` |

---

### **3\. Logical Operators**

Used to combine conditions:

| Operator | Description | Example |
| --- | --- | --- |
| `&&` | Logical AND | `(a > b) && (b > c)` |
| \` |  | \` |
| `!` | Logical NOT | `!(a > b)` |

---

### **4\. Bitwise Operators**

Used for bit-level operations:

| Operator | Description | Example |
| --- | --- | --- |
| `&` | AND | `a & b` |
| \` | \` | OR |
| `^` | XOR | `a ^ b` |
| `~` | Complement (NOT) | `~a` |
| `<<` | Left shift | `a << 2` |
| `>>` | Right shift | `a >> 2` |

---

### **5\. Assignment Operators**

Used to assign values to variables:

| Operator | Description | Example |
| --- | --- | --- |
| `=` | Assign | `a = 10` |
| `+=` | Add and assign | `a += b` |
| `-=` | Subtract and assign | `a -= b` |
| `*=` | Multiply and assign | `a *= b` |
| `/=` | Divide and assign | `a /= b` |
| `%=` | Modulus and assign | `a %= b` |

---

### **6\. Conditional (Ternary) Operator**

Shorthand for an `if-else` statement:

| Operator | Description | Example |
| --- | --- | --- |
| `? :` | Condition | `result = (a > b) ? a : b;` |

---

### **Example Code**

```csharp
using System;

class Program
{
    static void Main()
    {
        int a = 10, b = 5, c = 0;

        // Arithmetic Operators
        Console.WriteLine("Arithmetic Operations:");
        Console.WriteLine($"Addition (a + b): {a + b}");         // 15
        Console.WriteLine($"Subtraction (a - b): {a - b}");      // 5
        Console.WriteLine($"Multiplication (a * b): {a * b}");   // 50
        Console.WriteLine($"Division (a / b): {a / b}");         // 2
        Console.WriteLine($"Modulus (a % b): {a % b}");          // 0

        // Relational Operators
        Console.WriteLine("\\nRelational Comparisons:");
        Console.WriteLine($"Is a equal to b? (a == b): {a == b}"); // false
        Console.WriteLine($"Is a not equal to b? (a != b): {a != b}"); // true
        Console.WriteLine($"Is a greater than b? (a > b): {a > b}"); // true

        // Logical Operators
        Console.WriteLine("\\nLogical Operations:");
        Console.WriteLine($"Logical AND (a > 5 && b < 10): {(a > 5 && b < 10)}"); // true
        Console.WriteLine($"Logical OR (a < 5 || b < 10): {(a < 5 || b < 10)}");  // true
        Console.WriteLine($"Logical NOT !(a > b): {! (a > b)}");                 // false

        // Assignment Operators
        Console.WriteLine("\\nAssignment Operators:");
        c = a + b; // Assign sum of a and b to c
        Console.WriteLine($"Assign (c = a + b): {c}"); // 15
        c += b; // Add b to c
        Console.WriteLine($"Add and Assign (c += b): {c}"); // 20
        c *= 2; // Multiply c by 2
        Console.WriteLine($"Multiply and Assign (c *= 2): {c}"); // 40

        // Bitwise Operators
        Console.WriteLine("\\nBitwise Operations:");
        Console.WriteLine($"Bitwise AND (a & b): {a & b}"); // 0 (binary AND)
        Console.WriteLine($"Bitwise OR (a | b): {a | b}"); // 15 (binary OR)
        Console.WriteLine($"Bitwise XOR (a ^ b): {a ^ b}"); // 15 (binary XOR)
        Console.WriteLine($"Left Shift (a << 1): {a << 1}"); // 20
        Console.WriteLine($"Right Shift (a >> 1): {a >> 1}"); // 5

        // Conditional (Ternary) Operator
        Console.WriteLine("\\nConditional (Ternary) Operator:");
        string result = (a > b) ? "a is greater" : "b is greater";
        Console.WriteLine($"Result: {result}"); // "a is greater"

        // Null-coalescing Operator (??)
        Console.WriteLine("\\nNull-coalescing Operator:");
        string nullValue = null;
        string nonNullValue = nullValue ?? "Default Value";
        Console.WriteLine($"Null Value: {nonNullValue}"); // "Default Value"

        // Null-coalescing Assignment Operator (??=)
        Console.WriteLine("\\nNull-coalescing Assignment:");
        string nullableString = null;
        nullableString ??= "Assigned Default";
        Console.WriteLine($"Nullable String after ??=: {nullableString}"); // "Assigned Default"

        // Compound Operator Behavior with Pre/Post-Increment
        Console.WriteLine("\\nPre/Post-Increment Operators:");
        Console.WriteLine($"a: {a}, a++: {a++}, a after increment: {a}"); // 10, 10, 11
        Console.WriteLine($"b: {b}, ++b: {++b}, b after increment: {b}"); // 5, 6, 6

        // Tricky: Short-Circuiting with Logical Operators
        Console.WriteLine("\\nLogical Short-Circuiting:");
        int x = 0;
        bool shortCircuit = (x > 10) && (++x > 0); // x is not incremented because (x > 10) is false
        Console.WriteLine($"Value of x after short-circuit: {x}"); // 0

        // Tricky: Overflow Handling
        Console.WriteLine("\\nOverflow Handling:");
        int maxInt = int.MaxValue;
        unchecked // Overflow is ignored
        {
            int overflowResult = maxInt + 1; // Wraps around to negative
            Console.WriteLine($"Overflow Result (unchecked): {overflowResult}"); // -2147483648
        }
        checked // Overflow throws an exception
        {
            try
            {
                int checkedOverflow = maxInt + 1; // Throws OverflowException
            }
            catch (OverflowException ex)
            {
                Console.WriteLine($"Caught OverflowException: {ex.Message}");
            }
        }

        // Tricky: Operator Precedence
        Console.WriteLine("\\nOperator Precedence:");
        int precedenceResult = a + b * 2; // Multiplication has higher precedence than addition
        Console.WriteLine($"Result of a + b * 2: {precedenceResult}"); // 22
    }
}
```

### More about Operators

**Short-Circuiting in Logical Operators**

* `&&` and `||` operators stop evaluating as soon as the result is determined, which can affect side-effects like variable increments.
    

**Null-Coalescing Operator (**`??`) and Assignment (`??=`)

* Used to assign a value only if the left-hand operand is `null`.
    

**Overflow Behavior**

* `checked` and `unchecked` blocks control overflow behavior for arithmetic operations.
    
* By default, overflow is unchecked, which can cause silent errors in critical calculations.
    

**Operator Precedence**

* Multiplication, division, and modulus operators (``, /, %) have higher precedence than addition and subtraction (`+`,`` ). Use parentheses to avoid confusion.
    

**Bitwise vs Logical Operators**

* Bitwise operators (`&`, `|`) operate at the bit level, while logical operators (`&&`, `||`) evaluate entire boolean expressions.
    

# Params

The `params` keyword allows a method to accept a **variable number of arguments**. This is useful when the number of inputs is not known beforehand. It is used with **arrays** and enables passing multiple arguments without explicitly defining an array.

---

### **Important Points About** `params`:

1. **Variable Number of Arguments**:
    
    * The method can accept **zero or more arguments** of the specified type.
        
    * If no arguments are passed, the `params` array will have a length of 0.
        
2. **Only One** `params` Parameter:
    
    * A method can have only **one** `params` parameter.
        
    * It must be the **last parameter** in the method signature.
        
3. **Works With Arrays**:
    
    * If an array is passed, the `params` parameter directly references that array.
        
4. **Default Behavior**:
    
    * If no arguments are provided, the `params` array is initialized to an empty array.
        

---

### **Examples:**

### **1\. Simple Use of** `params`

```csharp
using System;

class Program
{
    static int Add(params int[] numbers)
    {
        int sum = 0;
        foreach (int num in numbers)
        {
            sum += num;
        }
        return sum;
    }

    static void Main(string[] args)
    {
        Console.WriteLine(Add(1, 2, 3, 4));  // Output: 10
        Console.WriteLine(Add());           // Output: 0
    }
}
```

---

### **2\. Passing an Array to** `params`

```csharp
using System;

class Program
{
    static void PrintNumbers(params int[] numbers)
    {
        foreach (var num in numbers)
        {
            Console.WriteLine(num);
        }
    }

    static void Main(string[] args)
    {
        int[] arr = { 10, 20, 30 };
        PrintNumbers(arr);  // Output: 10, 20, 30
    }
}
```

---

### **3\. Using** `params` with Objects

`params` can also accept a variable number of **objects**, enabling flexibility with data types.

```csharp
using System;

class Program
{
    static void Print(params object[] items)
    {
        foreach (var item in items)
        {
            Console.WriteLine(item);
        }
    }

    static void Main(string[] args)
    {
        Print(1, "hello", 3.5, true);
        // Output:
        // 1
        // hello
        // 3.5
        // True
    }
}
```

---

### **Interview-Level Tricky Concepts**

### **1\. Method Overloading with** `params`

If a method has overloads, the one with `params` will only be used when there is no **exact match** for the arguments.

```csharp
using System;

class Program
{
    static void Display(int a, int b)
    {
        Console.WriteLine($"Exact match: {a}, {b}");
    }

    static void Display(params int[] numbers)
    {
        Console.WriteLine("Using params:");
        foreach (var num in numbers)
        {
            Console.WriteLine(num);
        }
    }

    static void Main(string[] args)
    {
        Display(1, 2);         // Calls the exact match
        Display(1, 2, 3);      // Calls the params method
    }
}
```

---

### **2\. Combining** `params` with Regular Parameters

You can combine `params` with regular parameters, but the `params` parameter must come **last**.

```csharp
using System;

class Program
{
    static void Print(string prefix, params string[] words)
    {
        Console.WriteLine(prefix);
        foreach (var word in words)
        {
            Console.WriteLine(word);
        }
    }

    static void Main(string[] args)
    {
        Print("Words:", "apple", "banana", "cherry");
        // Output:
        // Words:
        // apple
        // banana
        // cherry
    }
}
```

---

### **3\. Passing Null or Empty Arrays**

Passing `null` or no arguments behaves differently. If no arguments are provided, the `params` array is initialized to an empty array. However, explicitly passing `null` will set the `params` parameter to null.

```csharp
using System;

class Program
{
    static void Print(params int[] numbers)
    {
        if (numbers == null)
        {
            Console.WriteLine("Params is null");
        }
        else
        {
            Console.WriteLine($"Array Length: {numbers.Length}");
        }
    }

    static void Main(string[] args)
    {
        Print();               // Output: Array Length: 0
        Print(null);           // Output: Params is null
    }
}
```

---

### **4\. Difference Between Passing Array Directly vs Using params**

When passing an array to a method with a **params** parameter, the method can use it directly, but without **params**, the method treats it as a single argument.

```csharp
using System;

class Program
{
    static void PrintNumbers(int[] numbers)
    {
        foreach (var num in numbers)
        {
            Console.WriteLine(num);
        }
    }

    static void Main(string[] args)
    {
        int[] arr = { 1, 2, 3 };

        PrintNumbers(arr);   // Valid
        // PrintNumbers(1, 2, 3); // Error: Requires an array
    }
}
```

---

### **Key Questions Asked in Interviews**

1. **Why can’t params be used with multiple parameters in a method?**
    
    It’s because the compiler wouldn’t be able to determine how to match arguments to parameters.
    
2. **What happens if an array is passed to a method with a params parameter?**
    
    The method directly uses the array without creating a new array.
    
3. **What is the difference between params and** `object` arrays?
    
    **params** simplifies the syntax by allowing multiple arguments, whereas an `object[]` array requires explicit array initialization.
    
4. **Can params be used with nullable reference types?**
    
    Yes, you can pass null values, but you should handle them carefully to avoid runtime exceptions.
    

# Type Casting

Type conversion happens when we assign the value of one data type to another. If the data types are compatible, then C# does **Automatic Type Conversion**. If not comparable, then they need to be converted explicitly which is known as **Explicit Type conversion**.

### **Implicit Type Casting / Automatic Type Conversion**

It happens when:

* The two data types are compatible.
    
* When we assign value of a smaller data type to a bigger data type.
    

```jsx
| From Data Type | To Data Types                     |
|----------------|-----------------------------------|
| byte           | → short, int, long, float, double |
| short          | → int, long, float, double        |
| int            | → long, float, double             |
| long           | → float, double                   |
| float          | → double                          |
```

If we want to assign a value of larger data type to a smaller data type we perform explicit type casting.

Sometimes, it may result into the **lossy conversion.**

```jsx
static void Main(String[] args){
           
           double d = 345.32; // example for lossy conversion
           int i  = (int)d; // implicit conversion
           Console.WriteLine(i); // 345
           // .32 is lost due to lossy conversion
        }
```

**C# provides built-in methods for Type-Conversions as follows**

```jsx
| Method     | Description                                             |
|------------|---------------------------------------------------------|
| ToBoolean  | Converts a type to a Boolean value                      |
| ToChar     | Converts a type to a character value                    |
| ToByte     | Converts a value to a Byte value                        |
| ToDecimal  | Converts a value to a Decimal point value               |
| ToDouble   | Converts a type to a double data type                   |
| ToInt16    | Converts a type to a 16-bit integer                     |
| ToInt32    | Converts a type to a 32-bit integer                     |
| ToInt64    | Converts a type to a 64-bit integer                     |
| ToString   | Converts a given type to a string                       |
| ToUInt16   | Converts a type to an unsigned 16-bit integer           |
| ToUInt32   | Converts a type to an unsigned 32-bit integer           |
| ToUInt64   | Converts a type to an unsigned 64-bit integer           |
```

**Code Example**

```csharp
static void Main(String[] args){
           
           double d = 345.32;
           string i  = Convert.ToString(d);
           Console.WriteLine(i); // string
           Console.WriteLine(Convert.ToUInt32(d));// .32 is lost
           Console.WriteLine(i.GetType());// System.String
           Console.WriteLine(Convert.ToDecimal(i)); //345.32
          
           
        }
```
