Mastering C# Part 2.2 - Fundamentals
Access Modifiers, Operators, Params, Type Casting, Enumeration, Structs, Nullable, Conditional statements, Jump Statements

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
publiccan be accessed from anywhere in the program or even from another assembly.Example:
public class Example { public int PublicValue = 10; // Accessible anywhere }
2. Private
Scope: Containing class.
Usage: Members marked as
privateare accessible only within the same class where they are declared.Example:
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
protectedare accessible within the same class or in derived classes.Example:
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
internalare accessible within the same assembly, Derived class but not from other assemblies.Example:
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 internalare accessible to:Derived classes (even in different assemblies).
Any code within the same assembly.
Example:
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 protectedare accessible:Only in derived classes within the same assembly.
Not accessible outside the assembly, even by derived classes.
Example:
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
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
checkedanduncheckedblocks 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:
Variable Number of Arguments:
The method can accept zero or more arguments of the specified type.
If no arguments are passed, the
paramsarray will have a length of 0.
Only One
paramsParameter:A method can have only one
paramsparameter.It must be the last parameter in the method signature.
Works With Arrays:
- If an array is passed, the
paramsparameter directly references that array.
- If an array is passed, the
Default Behavior:
- If no arguments are provided, the
paramsarray is initialized to an empty array.
- If no arguments are provided, the
Examples:
1. Simple Use of params
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
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.
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.
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.
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.
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.
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
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.
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.
What is the difference between params and
objectarrays?params simplifies the syntax by allowing multiple arguments, whereas an
object[]array requires explicit array initialization.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.
| 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.
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
| 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
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
}
