Mastering C# Part 2.3 - Fundamentals
Enumeration, Structs, Nullable, Conditional statements, Jump Statements

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:
enum EnumName
{
Constant1, // Automatically assigned value 0
Constant2, // Automatically assigned value 1
Constant3 // Automatically assigned value 2
}
Example:
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:
Day today = Day.Monday;
Console.WriteLine(today); // Output: Monday
You can also use enums in conditional statements:
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:
enum EnumName
{
Member1 = value1,
Member2 = value2,
Member3 = value3
}
Example:
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:
enum EnumName : BaseType
{
Member1 = value1,
Member2 = value2
}
Example:
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:
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:
// 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:
[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:
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
Flagsfor 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:
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:
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:
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:
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:
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:
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:
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:
Type? variableName;
2. Declaring Nullable Types
You can declare a nullable type by appending a ? to a value type.
Example:
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:
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:
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:
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:
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:
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:
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:
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?>:
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:
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.
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:
I found it but let's go to case 3 and execute The number is 3Explanation:
In the
switchstatement, whennumis 5, we usegoto case 3;to transfer control to the code block wherenumis 3. This allows us to skip the lines in between and reuse the code for a similar case.2. The
throwStatementThe
throwstatement is used to manually raise exceptions in C#. You create an exception object (typically derived from theExceptionclass) and throw it using thethrowkeyword.Example: Using
throwto Handle ExceptionsIn this example, we'll create a
NullReferenceExceptionmanually using thethrowstatement. We'll handle the exception usingtry-catch.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:
Subject cannot be nullExplanation:
In the method
displaySubject, we check if thesub1parameter isnull. If it is, we throw aNullReferenceException. Thetry-catchblock in theMainmethod 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
foreachto loop through an array of numbers and simulate some errors using thethrowstatement.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:
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: 6Explanation:
In this example:
We use a
foreachloop to process each number in thenumbersarray.If the number is negative, we throw an
ArgumentException.The
try-catchblock 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.
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:
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#:
goto: Used to transfer control to a labeled statement within the same method.throw: Used to throw exceptions manually, enabling error handling withtry-catchblocks.Combining Loops and Exception Handling: We saw how to combine loops like
foreachwith exception handling for more robust programs.
