0% found this document useful (0 votes)
3 views15 pages

Notes Unit 2 CNET

This document provides a comprehensive overview of C#.NET, covering its language features and the structure of .NET projects. It explains how to create and organize projects, use namespaces, implement inheritance, understand data types, and perform type conversions. Additionally, it discusses the concept of assemblies as the fundamental unit of deployment in .NET applications.

Uploaded by

kbot9607
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views15 pages

Notes Unit 2 CNET

This document provides a comprehensive overview of C#.NET, covering its language features and the structure of .NET projects. It explains how to create and organize projects, use namespaces, implement inheritance, understand data types, and perform type conversions. Additionally, it discusses the concept of assemblies as the fundamental unit of deployment in .NET applications.

Uploaded by

kbot9607
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

C#.NET Language Features & .

NET Project
Structure
Welcome to the world of C#.NET! In this comprehensive guide, we'll explore the powerful features of C# as a programming
language and understand how the .NET framework provides a robust foundation for building modern applications. Think of
.NET as a complete toolkit for software development—it's like having a fully equipped workshop where C# is your primary
tool, and the .NET libraries are all the specialized equipment that helps you build anything from simple console apps to
complex enterprise systems.

This unit focuses on understanding both the language features that make C# powerful and the architectural components that
make .NET flexible and scalable. We'll learn how to organize code professionally using namespaces, work with assemblies to
create reusable components, and leverage the Base Class Library to avoid reinventing the wheel. By the end, you'll be
comfortable creating well-structured projects, debugging code effectively, and using essential .NET features for real-world
development.
Creating Your First .NET Project
Creating a .NET project is the foundation of all C# development. When you create a project in Visual Studio, you're essentially
setting up a structured workspace where all your code, resources, and configuration files will live. There are different project
types available, but the two most fundamental ones are Console Applications and Class Libraries. A Console Application is a
program that runs in a command-line window—perfect for learning and building utility tools. A Class Library, on the other
hand, creates reusable code components (DLL files) that other applications can reference and use.

Console Application Class Library


Creates an executable (.exe) program that runs in the Creates a reusable library (.dll) containing classes and
terminal. Used for command-line tools, utilities, and methods. Cannot run independently—must be
learning projects. Has a Main() method as the entry referenced by other projects. Used for shared code and
point. component development.

Every .NET project contains a project file with the .csproj extension. Think of this file as the blueprint for your project—it tells
the build system what files to include, which .NET version to target, what dependencies you need, and other important
configuration details. When you click "Build" in Visual Studio, the compiler reads this file, compiles your C# code into
Intermediate Language (IL), and packages everything into an assembly (either a .exe or .dll file). This build process is what
transforms your human-readable code into something the .NET runtime can execute.

Project Structure Build Process


[Link] - Main entry point 1. Compiler reads .csproj file
[Link] - Project configuration 2. Compiles .cs files to IL code
bin/ - Compiled output files 3. Creates assembly with metadata
obj/ - Intermediate build files 4. Outputs to bin/Debug or bin/Release
Properties/ - Assembly metadata 5. Ready to execute via .NET runtime

// Basic Console Application Structure


using System;

namespace MyFirstProject
{
class Program
{
static void Main(string[] args)
{
[Link]("Hello, .NET World!");
[Link]();
}
}
}

The code above shows the minimal structure of a C# console application. The using System; statement imports the System
namespace, giving you access to fundamental classes like Console. The namespace declaration organizes your code, and
the Main() method is where program execution begins. Every console application must have exactly one Main() method—it's
the doorway through which the .NET runtime enters your application.
Namespaces and Classes: Organizing Your Code
As projects grow larger, organizing code becomes critical. Imagine trying to find a specific book in a library where thousands
of books are piled randomly on the floor—it would be chaos! Namespaces solve this problem in C# by providing a hierarchical
organization system for your classes, similar to how folders organize files on your computer. A namespace groups related
classes together and prevents naming conflicts when you have multiple classes with the same name in different parts of your
application.

For example, you might have a Student class in a university management system, but you might also need a Student class in
an online course platform. Without namespaces, these would conflict. By placing one in [Link] and another
in [Link], you can use both without any problems. The full name becomes [Link]
and [Link]—completely distinct identities.

Why Use Namespaces? Namespace Naming Convention


Prevent naming conflicts between classes Use PascalCase (each word capitalized)
Logically group related functionality Follow pattern: [Link]

Make code easier to navigate and maintain Example: [Link]


Control access and visibility of types Be descriptive but concise

// File: Models/[Link]
namespace [Link]
{
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
}

// File: Services/[Link]
using [Link]; // Import the namespace

namespace [Link]
{
public class StudentService
{
public void EnrollStudent(Student student)
{
// Use Student class from Models namespace
[Link]($"Enrolling {[Link]}");
}
}
}

The using directive at the top of a file allows you to reference classes from other namespaces without typing the full
namespace path every time. Instead of writing [Link] repeatedly, you can just write Student after
adding using [Link]; at the top. This keeps your code clean and readable while maintaining the
organizational benefits of namespaces.

Best Practice: Organize your files in folders that mirror your namespace structure. If you have a namespace called
[Link], create folders Data/Repositories/ and place the corresponding class files there. This
makes your project intuitive to navigate.
Inheritance: Building on Existing Classes
Inheritance is one of the fundamental pillars of object-oriented programming, allowing you to create new classes based on
existing ones. Think of inheritance like genetic inheritance in biology—a child inherits characteristics from their parents but
can also have their own unique features. In C#, when a class inherits from another class, it automatically gets all the public
and protected members (fields, properties, methods) of the parent class, and can add its own specialized functionality.

This concept is incredibly powerful for code reuse and creating hierarchical relationships. For example, you might have a
base Person class with common properties like Name and Age. Then you can create specialized classes like Student and
Teacher that inherit from Person, automatically getting those basic properties while adding their own specific ones like
StudentId or Subject.

Inheritance Benefits
// Base Class
public class Person Eliminates code duplication
{ Establishes IS-A relationships
public string Name { get; set; }
Enables polymorphism
public int Age { get; set; }
Improves maintainability

public void Introduce()


Key Points
{
[Link]( Use colon (:) to inherit
$"Hi, I'm {Name}"); C# supports single inheritance only
}
Child class gets all public/protected members
}
Can override virtual methods

// Derived Class
public class Student : Person
{
public string StudentId { get; set; }
public double GPA { get; set; }

public void Study()


{
[Link](
$"{Name} is studying");
}
}

// Using the inherited classes


class Program
{
static void Main(string[] args)
{
Student student = new Student();
[Link] = "Sarah"; // Inherited from Person
[Link] = 20; // Inherited from Person
[Link] = "S12345"; // Student's own property
[Link] = 3.8; // Student's own property

[Link](); // Inherited method


[Link](); // Student's own method
}
}

When designing class hierarchies, follow the "IS-A" rule: inheritance should only be used when the derived class truly IS-A
type of the base class. A Student IS-A Person, so inheritance makes sense. But a Car is not a Person, so inheriting Car from
Person would be incorrect. In C#, a class can only inherit from one base class (single inheritance), but can implement multiple
interfaces, giving you flexibility in design while maintaining simplicity.

1 2 3

Base Class Derived Class Specialized Use


Defines common behavior Inherits and extends Polymorphic behavior
Understanding Data Types in C#
C# is a strongly typed language, which means every variable must have a specific data type declared when it's created, and
that type cannot change. This is like labeling storage containers in a warehouse—once you designate a container for
"electronics," you can't suddenly start storing food in it. This strictness helps prevent errors and makes your code more
predictable and maintainable. Understanding data types is crucial because choosing the right type affects memory usage,
performance, and what operations you can perform on your data.

Data types in C# fall into two major categories: value types and reference types. Value types store their data directly in
memory (on the stack), while reference types store a reference (memory address) to where the actual data lives (on the
heap). This distinction affects how variables are copied, compared, and managed in memory. Let's explore the most
commonly used data types you'll work with in everyday programming.

int double string bool


Integer numbers from Decimal numbers with Text data, sequence of True or false values.
-2,147,483,648 to high precision. Use for characters. Use for Perfect for flags,
2,147,483,647. Perfect scientific calculations, names, messages, any conditions, yes/no
for counting, IDs, ages. measurements. text content. scenarios.

int age = 25; double price = 99.99; string name = "John"; bool isActive = true;

char decimal
Single character enclosed in single quotes. Stores one High-precision decimal for money. More accurate than
Unicode character. double for financial calculations.

char grade = 'A'; decimal salary = 50000.50m;

// Data Type Examples with Common Operations


class DataTypeDemo
{
static void Main()
{
// Integer types
int studentCount = 150;
long worldPopulation = 8000000000L;

// Floating-point types
double pi = 3.14159;
float temperature = 98.6f;
decimal accountBalance = 1250.75m;

// Boolean
bool isPassed = true;
bool isRaining = false;

// Character and String


char initial = 'J';
string fullName = "John Doe";

// Performing operations
int total = studentCount + 50;
double area = pi * 5 * 5;
string greeting = "Hello, " + fullName;

[Link]($"Total students: {total}");


[Link]($"Circle area: {area}");
[Link](greeting);
}
}

Value Types Reference Types


int, double, float, decimal string, object
bool, char class, interface
struct, enum arrays, delegates
Stored on stack Stored on heap
Copying creates independent copy Copying shares same reference

C# also provides the var keyword for implicit typing, where the compiler automatically determines the type based on the
assigned value. For example, var number = 10; creates an int, and var name = "Alice"; creates a string. This doesn't make C#
dynamically typed—the type is still fixed at compile time; you're just letting the compiler figure it out. Use var when the type is
obvious from the assignment to keep code concise, but avoid it when clarity would suffer.

Nullable Types: By default, value types cannot be null. If you need to represent "no value," use nullable types with
the ? syntax: int? age = null;. This is essential when working with databases where fields might be empty.
Type Conversion: Implicit vs Explicit Casting
Type conversion is the process of converting a value from one data type to another. This happens frequently in programming
—you might need to convert user input from a string to a number, or combine an integer with a decimal in a calculation. C#
provides two types of conversion: implicit (automatic) and explicit (manual). Understanding when each type occurs and how
to use them correctly is essential for writing robust code that handles data transformations safely.

Implicit conversion happens automatically when there's no risk of data loss. Think of it like pouring water from a small cup
into a large bucket—everything fits without problems. For example, converting an int to a double happens implicitly because
every integer value can be represented as a double. The compiler does this conversion for you automatically, no special
syntax required. However, going the other direction requires explicit conversion because you might lose the decimal portion.

01 02 03

Implicit Conversion Explicit Conversion (Casting) Conversion Methods


Safe conversions that happen Manual conversions where data loss is Using Convert class or Parse methods
automatically without data loss. From possible. Requires cast operator with for string-to-type conversions with error
smaller to larger types. parentheses. handling.

// Implicit Conversion Examples (Automatic)


int wholeNumber = 100;
double decimalNumber = wholeNumber; // int → double (safe)
[Link](decimalNumber); // Output: 100.0

float smallDecimal = 5.5f;


double largeDecimal = smallDecimal; // float → double (safe)
// Explicit Conversion (Manual Casting)
double price = 99.99;
int roundedPrice = (int)price; // double → int (loses .99)
[Link](roundedPrice); // Output: 99

long bigNumber = 5000000000L;


int smallerNumber = (int)bigNumber; // Risky: might overflow

// Using Convert Class (Recommended for safety)


string numberText = "42";
int number = Convert.ToInt32(numberText);
double piValue = [Link]("3.14159");
bool isTrue = [Link]("true");

// Using Parse Methods


string ageText = "25";
int age = [Link](ageText);
double temperature = [Link]("98.6");

// TryParse for Safe Conversion (handles errors gracefully)


string input = "not a number";
int result;
if ([Link](input, out result))
{
[Link]($"Conversion successful: {result}");
}
else
{
[Link]("Invalid input, conversion failed");
}

When to Use Each Method Common Conversion Scenarios


Implicit: Happens automatically, no action needed User input (string) → int/double
Math operations mixing int and double
Casting: When you're certain about the conversion and
accept potential data loss Database values → C# types
API responses → typed objects
Convert class: Converting between different types,
especially from strings Displaying numbers as formatted strings

TryParse: User input or external data where validity is


uncertain

The TryParse method is particularly useful when converting user input or data from external sources. Instead of throwing an
exception when conversion fails (like Parse does), it returns a boolean indicating success or failure and outputs the converted
value through an out parameter. This pattern lets you handle invalid input gracefully without crashing your program—essential
for building user-friendly applications.

Common Mistake: Trying to assign a double to an int without casting will cause a compilation error. Always use
explicit casting with (int) when converting from larger to smaller types, and be aware you'll lose the decimal portion.
Assemblies: The Building Blocks of .NET
Applications
An assembly is the fundamental unit of deployment in .NET—it's the compiled output of your code packaged into a reusable
format. Think of an assembly as a shipping container for your code: it packages everything needed to run your program into a
single file (or set of files) that can be deployed, versioned, and executed. When you build a C# project, the compiler produces
an assembly—either an executable (.exe) that can run independently, or a library (.dll) that other programs can use.

Every assembly contains four key components: the code itself (compiled into Intermediate Language or IL), metadata about
the types defined in the assembly, a manifest that describes the assembly's identity and dependencies, and optional
resources like images or text files. The manifest is particularly important—it's like a shipping label that tells the .NET runtime
what this assembly is called, what version it is, what other assemblies it depends on, and what security permissions it needs.

Executable Assembly (.exe) Library Assembly (.dll) Assembly Manifest


Contains a Main() entry point and can Contains reusable code but cannot run Metadata describing the assembly's
run independently. Used for on its own. Must be referenced by identity, version, dependencies, and
applications that users directly execute. executable projects to be used. security requirements.

// Viewing Assembly Information in Code


using System;
using [Link];

class AssemblyDemo
{
static void Main()
{
// Get information about the current assembly
Assembly currentAssembly = [Link]();

[Link]("Assembly Name: " + [Link]);


[Link]("Location: " + [Link]);
[Link]("Version: " + [Link]().Version);

// List all types (classes) in the assembly


Type[] types = [Link]();
[Link]("\nTypes in this assembly:");
foreach (Type type in types)
{
[Link](" - " + [Link]);
}
}
}

Assembly Components Why Assemblies Matter


IL Code: Compiled intermediate language Enable code reuse across projects
Metadata: Type definitions and members Support versioning and updates
Manifest: Assembly identity and version Provide deployment units
Resources: Embedded files and data Enforce security boundaries
Enable side-by-side execution

Assemblies can be private or shared. Private assemblies are copied into each application's directory and used only by that
application—this is the default and simplest approach. Shared assemblies (also called strong-named assemblies) are installed
in the Global Assembly Cache (GAC) and can be used by multiple applications simultaneously. Shared assemblies require a
strong name (created with a cryptographic key) to ensure versioning and prevent tampering.

When you reference another assembly in your project, you're telling the compiler "I need to use code from this other
assembly." The manifest in your assembly will record this dependency, and at runtime, the .NET runtime will locate and load
the referenced assemblies automatically. This dependency system allows you to build modular applications where different
assemblies handle different responsibilities—one for data access, another for business logic, another for user interface, all
working together seamlessly.

Key Concept: The metadata in assemblies is what makes .NET's reflection capabilities possible. At runtime, you can
examine assemblies to discover what types they contain, what methods those types have, and even invoke methods
dynamically without knowing about them at compile time.
Namespaces: Organizing Large Projects
As your .NET projects grow from simple programs to complex applications with hundreds or thousands of classes,
organization becomes critical. Namespaces provide a hierarchical structure for grouping related code, much like how a file
system uses folders to organize documents. Without namespaces, all your classes would exist in a flat global space, leading
to naming conflicts and confusion. With namespaces, you can have multiple classes with the same name as long as they're in
different namespaces—they become distinct entities with unique fully qualified names.

The .NET Framework itself is organized using a comprehensive namespace hierarchy. The root namespace is System, and
underneath it are hundreds of sub-namespaces like [Link] for file operations, [Link] for data structures, and
[Link] for networking. This hierarchical naming follows a pattern: the more specific the functionality, the deeper the
namespace. For example, [Link] contains generic collection types, which is more specific than just
[Link].

System [Link]
Root namespace File and stream handling
organizing core APIs subnamespaces

[Link] [Link]
Collections, generics, and Networking, HTTP, and
data structures sockets namespaces

This hierarchical organization helps developers quickly locate functionality and understand the relationships between
different components.

// Defining and using namespaces across multiple files

// File: DataAccess/[Link]
namespace [Link]
{
public class Repository
{
public void SaveData()
{
[Link]("Saving data to database");
}
}
}

// File: Business/[Link]
namespace [Link]
{
// Import the DataAccess namespace
using [Link];

public class OrderProcessor


{
private Repository _repository = new Repository();

public void ProcessOrder()


{
[Link]("Processing order");
_repository.SaveData();
}
}
}

// File: [Link]
using [Link];

class Program
{
static void Main()
{
OrderProcessor processor = new OrderProcessor();
[Link]();
}
}

1 2 3

Prevent Conflicts Logical Grouping Access Control


Multiple classes can have the same Related classes organized together Control visibility across project
name if in different namespaces for easy discovery boundaries

Namespace Best Practices Using Directive Features


Follow [Link] pattern
// Standard using
Use PascalCase naming convention
using System;
Mirror folder structure in namespaces
Keep namespace depth reasonable (3-5 levels) // Alias for long namespace

Avoid namespace names matching class names using Proj = [Link];

// Static using (C# 6.0+)


using static [Link];
WriteLine("No need for Console.");

// Global using (C# 10.0+)


global using [Link];

The using directive makes your code cleaner by allowing you to reference types without their full namespace path. Instead of
writing [Link]<int> throughout your code, you can add using [Link]; at the top
and just write List<int>. Modern C# also supports static using (to import static members) and global using (to apply a using
statement across all files in a project), further reducing repetitive code.

Common Convention: The default namespace for a project matches the project name. When you create a new
class file in Visual Studio, it automatically uses the appropriate namespace based on the folder location within your
project structure.
Exploring the Base Class Library (BCL)
The Base Class Library (BCL) is .NET's treasure chest of pre-built functionality—thousands of classes that handle common
programming tasks so you don't have to reinvent the wheel. Imagine building a house: you could forge your own nails, cut
your own lumber, and create every component from scratch, or you could use manufactured materials and focus on the
actual construction. The BCL provides those "manufactured materials" for software development—ready-to-use, tested, and
optimized code for everything from reading files to making HTTP requests.

The BCL is organized into namespaces that group related functionality. Some of the most frequently used namespaces
include System (fundamental types and basic operations), [Link] (file and stream operations), [Link]
(data structures like lists and dictionaries), [Link] (string manipulation and encoding), [Link] (querying
collections), and [Link] (networking operations). Learning to navigate and use the BCL effectively is one of the most
valuable skills for a .NET developer.

[Link] [Link]
File and directory operations, streams, readers, and Type-safe data structures like List, Dictionary,
writers. Essential for reading/writing data to disk, Queue, and Stack. Use these instead of arrays for
managing files, and working with different data dynamic, resizable collections with rich functionality.
formats.

[Link] [Link]
String manipulation, StringBuilder for efficient string Mathematical operations like trigonometric functions,
operations, and encoding/decoding for different logarithms, power, rounding, and constants like Pi.
character sets. Critical for processing text data. Used in scientific and financial calculations.

[Link] [Link]
Network communication including HTTP requests, Language Integrated Query for filtering, sorting, and
TCP/IP sockets, and email. Build web clients, REST transforming collections. Write expressive, SQL-like
API consumers, and networked applications. queries on any enumerable data source.

// BCL Usage Examples - Common Scenarios

using System;
using [Link];
using [Link];
using [Link];
using [Link];

class BCLDemo
{
static void Main()
{
// [Link] - Mathematical operations
double result = [Link](16);
double power = [Link](2, 10);
[Link]($"Square root: {result}, Power: {power}");

// [Link] - File operations


string filePath = "[Link]";
[Link](filePath, "Hello from BCL!");
string content = [Link](filePath);

// [Link] - Lists
List students = new List
{
"Alice", "Bob", "Charlie", "Diana"
};
[Link]("Edward");

// [Link] - Querying collections


var aStudents = [Link](s => [Link]("A"));

// [Link] - StringBuilder for efficient concatenation


StringBuilder builder = new StringBuilder();
for (int i = 0; i < [Link]; i++)
{
[Link]($"{i + 1}. {students[i]}");
}
[Link]([Link]());
}
}

One of the most powerful aspects of the BCL is that it's consistent across different types of .NET applications. Whether you're
building a console app, a web application, a mobile app, or a Windows desktop program, the same BCL classes work the
same way. This consistency dramatically reduces the learning curve—once you learn how to use List<T> or File or HttpClient,
you can apply that knowledge across any .NET project type.

Pro Tip: Before writing code to solve a common problem, check if the BCL already has a solution. Need to sort a list?
Use [Link](). Need to format dates? Use [Link](). The BCL documentation ([Link]) is
comprehensive and includes examples for almost every class and method.
Debugging and Error Handling in C#
Debugging is the process of finding and fixing errors in your code—it's an essential skill that separates professional
developers from beginners. Visual Studio provides powerful debugging tools that let you pause execution, inspect variable
values, step through code line by line, and understand exactly what's happening inside your program. Think of debugging like
being a detective: you gather clues (variable values), follow leads (step through code paths), and eventually solve the mystery
(find the bug).

The most fundamental debugging tool is the breakpoint—a marker you place on a line of code where you want execution to
pause. When your program reaches a breakpoint during debugging, it stops completely, giving you a chance to examine the
current state. You can inspect variable values, check object properties, evaluate expressions, and see the call stack (the
sequence of method calls that led to this point). From a breakpoint, you can step to the next line, step into method calls to see
their internal workings, or step over calls to skip their details.

Breakpoints Step Into (F11)


Click in the left margin to set a breakpoint. Program Execute one line and follow into method calls to see their
pauses at this line when debugging. Press F9 to toggle. implementation. Use to debug inside methods.

Step Over (F10) Watch Window


Execute one line but don't follow into method calls. Use Monitor specific variables or expressions as you step
when you trust the method works correctly. through code. See how values change over time.

While debugging helps you find errors during development, error handling ensures your program behaves gracefully when
something goes wrong at runtime. Not all errors can be prevented—users might enter invalid data, files might not exist,
network connections might fail. The try-catch-finally structure lets you anticipate potential errors, handle them appropriately,
and ensure cleanup code always runs. Think of it like a safety net: the try block is the tightrope walk, the catch block is the
net that catches you if you fall, and the finally block is the crew that cleans up regardless of what happened.

// Error Handling with Try-Catch-Finally


using System;
using [Link];

class ErrorHandlingDemo
{
static void Main()
{
// Example 1: Handling division by zero
try
{
[Link]("Enter a number: ");
int number = [Link]([Link]());
int result = 100 / number;
[Link]($"100 / {number} = {result}");
}
catch (DivideByZeroException ex)
{
[Link]("Error: Cannot divide by zero!");
[Link]($"Details: {[Link]}");
}
catch (FormatException ex)
{
[Link]("Error: Please enter a valid number!");
}
catch (Exception ex)
{
[Link]($"Unexpected error: {[Link]}");
}
finally
{
[Link]("Operation completed.");
}

// Example 2: File operations with error handling


FileStream fs = null;
try
{
fs = new FileStream("[Link]", [Link]);
// Read from file...
}
catch (FileNotFoundException)
{
[Link]("File not found. Creating new file.");
fs = new FileStream("[Link]", [Link]);
}
finally
{
// Always close the file, whether error occurred or not
if (fs != null)
{
[Link]();
[Link]("File closed properly.");
}
}
}
}

Common Exception Types Best Practices

NullReferenceException - Using a null object Catch specific exceptions before general ones

IndexOutOfRangeException - Invalid array index Use finally for cleanup code (closing files, connections)

FormatException - Invalid data format Don't swallow exceptions silently (empty catch)

FileNotFoundException - Missing file Log exceptions for troubleshooting

DivideByZeroException - Division by zero Throw custom exceptions for business logic errors

You can also create custom exceptions by inheriting from the Exception class. This is useful when you want to signal specific
error conditions in your application that aren't covered by the standard exception types. For example, you might create an
InsufficientFundsException for a banking application or a InvalidStudentIdException for a student management system.

Debugging Tip: Use the Immediate Window (Debug → Windows → Immediate) during a debugging session to
execute code and evaluate expressions on the fly. You can call methods, change variable values, and test
hypotheses without modifying your source code.
String Manipulation in C#
Strings are one of the most commonly used data types in programming—nearly every application works with text data in
some form. In C#, the String class is immutable, meaning once created, a string cannot be changed. Any operation that
appears to modify a string actually creates a new string object. This immutability ensures thread safety and prevents
accidental modifications, but it also means that repeatedly concatenating strings in a loop is inefficient because each
concatenation creates a new string object in memory.

The String class provides dozens of methods for manipulating text: searching, replacing, splitting, combining, changing case,
trimming whitespace, and much more. Understanding these methods and when to use them is crucial for processing user
input, formatting output, parsing data files, and countless other text-related tasks. Let's explore the most commonly used
string operations with practical examples you'll encounter in real-world development.

Searching & Extracting & Case Combining &


Checking Cutting Conversion Replacing
Contains(), Substring(), Split(), ToUpper(), Concat(), Join(),
StartsWith(), Trim() - Extract ToLower(), Replace() -
EndsWith(), portions of strings, ToTitleCase() - Combine multiple
IndexOf() - Find text divide into parts, or Change letter strings or replace
within strings or remove extra casing for text. Use
check for patterns. whitespace. Key for normalization, StringBuilder for
Essential for processing display, or heavy
validation and formatted data. comparison concatenation in
parsing. purposes. loops.

// String Manipulation Examples


using System;
using [Link];

class StringDemo
{
static void Main()
{
string name = " John Doe ";
string email = "[Link]@[Link]";

// Basic operations
[Link]([Link]()); // Remove whitespace
[Link]([Link]()); // Convert to uppercase
[Link]([Link]()); // Convert to lowercase

// Searching and checking


bool hasJohn = [Link]("John"); // Check if contains
bool startsWithJ = [Link]().StartsWith("J"); // Check start
int atPosition = [Link]("@"); // Find character position

// Extracting
string firstName = [Link]().Substring(0, 4); // Get first 4 chars
string domain = [Link](atPosition + 1); // After @ symbol

// Splitting
string[] parts = [Link]().Split(' ');
[Link]($"First: {parts[0]}, Last: {parts[1]}");

// Replacing
string masked = [Link]("john", "****");
[Link](masked);

// String formatting
int age = 25;
double salary = 50000.50;
string info = [Link]("Name: {0}, Age: {1}, Salary: {2:C}",
[Link](), age, salary);
[Link](info);

// String interpolation (modern approach)


string message = $"{[Link]()} is {age} years old";

// StringBuilder for efficient concatenation


StringBuilder builder = new StringBuilder();
for (int i = 1; i <= 100; i++)
{
[Link]($"Number {i}\n");
}
// Much more efficient than string concatenation in loop!
}
}

String vs StringBuilder String Comparison


String: Immutable, creates new object on each Use proper methods for comparing strings:
modification. Fine for occasional operations.
== operator - Exact match
StringBuilder: Mutable, modifies internal buffer. Use for Equals() - Case-sensitive equality
loops with many concatenations (10+ operations).
Equals([Link]) - Case-
insensitive
// Inefficient
CompareTo() - Alphabetical ordering
string result = "";
for (int i = 0; i < 1000; i++)
result += i; // Creates 1000 strings! string a = "hello";
string b = "HELLO";
// Efficient
StringBuilder sb = new StringBuilder(); // Different results
for (int i = 0; i < 1000; i++) a == b // false
[Link](i); // Modifies one buffer [Link](b, StringComparison
.OrdinalIgnoreCase) // true

String interpolation (using $ prefix) is the modern, readable way to embed expressions in strings. Instead of writing
[Link]("Hello {0}", name), you can write $"Hello {name}". This makes code much clearer, especially with multiple
variables or expressions. You can even include method calls and calculations directly: $"Total: {price * quantity:C}" (the :C
formats as currency).

Performance Tip: If you need to concatenate strings in a loop more than 5-10 times, use StringBuilder. Each string
concatenation creates a new string object and copies all previous characters, making loops with hundreds or
thousands of iterations extremely slow with regular strings.
File Handling and Input/Output Operations
File handling is essential for creating applications that persist data beyond program execution. Whether you're saving user
preferences, logging errors, importing data, or generating reports, you need to read from and write to files. The .NET
Framework provides comprehensive support for file operations through the [Link] namespace, offering both simple
methods for common tasks and powerful stream-based APIs for advanced scenarios.

There are two main approaches to file I/O in C#: convenience methods and stream-based operations. Convenience methods
like [Link]() and [Link]() are perfect for simple scenarios—they handle everything in one line of code.
Stream-based operations using StreamReader and StreamWriter give you more control, allowing you to read or write files line
by line or in chunks, which is essential for large files that won't fit entirely in memory.

01 02

Check File Exists Choose Method


Use [Link]() to verify file presence before operations to Simple files: use File class methods. Large files or line-by-
avoid exceptions line: use Streams

03 04

Handle Errors Close Resources


Wrap file operations in try-catch to handle missing files, Always close streams with using statement to prevent
permission issues resource leaks

// File Handling Examples


using System;
using [Link];

class FileDemo
{
static void Main()
{
// Example 1: Simple read/write with File class
string filePath = "student_data.txt";

// Write to file (creates or overwrites)


[Link](filePath, "John Doe, 85\nJane Smith, 92\n");

// Append to file
[Link](filePath, "Bob Johnson, 78\n");

// Read entire file


string content = [Link](filePath);
[Link]("File contents:\n" + content);

// Read all lines into array


string[] lines = [Link](filePath);
foreach (string line in lines)
{
string[] parts = [Link](',');
[Link]($"Student: {parts[0]}, Grade: {parts[1]}");
}

// Example 2: StreamWriter for efficient writing


using (StreamWriter writer = new StreamWriter("[Link]"))
{
[Link]("Header Line");
for (int i = 1; i <= 100; i++)
{
[Link]($"Line {i}: Some data here");
}
} // Automatically closes the stream

// Example 3: StreamReader for line-by-line reading


if ([Link]("[Link]"))
{
using (StreamReader reader = new StreamReader("[Link]"))
{
string line;
int lineNumber = 0;
while ((line = [Link]()) != null)
{
lineNumber++;
[Link]($"{lineNumber}: {line}");

// Can process huge files without loading all into memory


if (lineNumber >= 5) break; // Just show first 5 lines
}
}
}

// Example 4: Error handling with files


try
{
string data = [Link]("[Link]");
}
catch (FileNotFoundException)
{
[Link]("File not found!");
}
catch (UnauthorizedAccessException)
{
[Link]("No permission to access file!");
}
catch (IOException ex)
{
[Link]($"I/O error: {[Link]}");
}
}
}

File Class Methods When to Use Streams


Simple, one-line operations: Large files (100+ MB) - avoid loading all into memory
Line-by-line processing (log files, CSV data)
[Link]() - Write string to file
Real-time data writing (logging during execution)
[Link]() - Read entire file as string
Binary files (images, audio, custom formats)
[Link]() - Add to end of file
Network streams or compression
[Link]() - Read file as string array

[Link]() - Check if file exists Using Statement: Automatically disposes resources even if
exception occurs. Always use with streams!
[Link]() - Remove a file

[Link]() - Copy file to new location

The using statement is crucial when working with streams and file handles. Files are system resources that need to be
properly closed after use—forgetting to close them can lead to locked files, resource leaks, and eventually program crashes.
The using statement ensures that the file handle is closed and resources are released, even if an exception occurs during
processing. It's like borrowing a book from the library—the using statement guarantees you return it, even if you get
interrupted while reading.

Path Handling: Use [Link]() to build file paths instead of string concatenation. This handles different path
separators (/ vs \) automatically across operating systems: [Link]("C:", "Data", "[Link]") works correctly on
Windows, Linux, and Mac.
Collections: Managing Groups of Data
Collections are data structures that hold multiple related items, providing a more flexible and powerful alternative to arrays.
While arrays have a fixed size that must be determined at creation, collections like List, Dictionary, Queue, and Stack can
grow or shrink dynamically as you add or remove items. Think of an array as a parking lot with numbered spaces—you must
know how many spaces you need upfront. A collection is more like a flexible parking system that expands as more cars
arrive.

The .NET Framework provides both generic and non-generic collections. Generic collections (in [Link])
are type-safe—they only hold one specific type of object, catching type errors at compile time. Non-generic collections (in
[Link]) can hold any type of object, but you lose type safety and performance. Always prefer generic collections
in modern C# development unless you have a specific reason to use non-generic ones.

List<T> Dictionary<TKey, Queue<T> Stack<T>


Dynamic array that can TValue> First-In-First-Out (FIFO) Last-In-First-Out (LIFO)
grow/shrink. Best for ordered Stores key-value pairs for collection. Items added to collection. Items added and
collections where you need fast lookup by key. Perfect end, removed from front. removed from top. Use for
index access, sorting, and for mappings like student ID Use for task scheduling, undo functionality,
flexible size. Most commonly to student object, or word to message processing. expression evaluation.
used collection. definition.
Queue<string> tasks = new Stack<int> history = new
List<string> names = new Dictionary<string, int> ages = Queue<string>(); Stack<int>();
List<string>(); new Dictionary<string, int>();

// Collection Examples
using System;
using [Link];

class CollectionDemo
{
static void Main()
{
// List - Most common collection
List students = new List();
[Link]("Alice");
[Link]("Bob");
[Link]("Charlie");
[Link](1, "David"); // Insert at specific position
[Link]("Bob"); // Remove by value
[Link](0); // Remove by index

[Link]($"Count: {[Link]}");
foreach (string student in students)
{
[Link](student);
}

// Dictionary - Key-value pairs


Dictionary grades = new Dictionary();
grades["Alice"] = 85;
grades["Bob"] = 92;
grades["Charlie"] = 78;

// Access by key
[Link]($"Alice's grade: {grades["Alice"]}");

// Check if key exists before accessing


if ([Link]("David"))
{
[Link](grades["David"]);
}
else
{
[Link]("David not found");
}

// Iterate through dictionary


foreach (KeyValuePair entry in grades)
{
[Link]($"{[Link]}: {[Link]}");
}

// Queue - FIFO (First In, First Out)


Queue printQueue = new Queue();
[Link]("[Link]");
[Link]("[Link]");
[Link]("[Link]");

while ([Link] > 0)


{
string doc = [Link](); // Remove from front
[Link]($"Printing: {doc}");
}

// Stack - LIFO (Last In, First Out)


Stack browserHistory = new Stack();
[Link]("[Link]");
[Link]("[Link]");
[Link]("[Link]");

// Go back through history


[Link]($"Current: {[Link]()}"); // Look without removing
[Link](); // Remove from top
[Link]($"After back: {[Link]()}");
}
}

Choosing the Right Collection Common List Operations


Need order + index access? → List<T>
List<int> numbers = new List<int>();
Need fast lookup by key? → Dictionary<TKey, TValue>
Need unique items only? → HashSet<T> // Adding
Need FIFO processing? → Queue<T> [Link](10);

Need LIFO processing? → Stack<T> [Link](new int[] {20, 30});

Need sorted items? → SortedList or SortedSet


// Removing
[Link](10); // By value
[Link](0); // By index
[Link](); // All

// Searching
bool has20 = [Link](20);
int index = [Link](30);

// Sorting
[Link]();
[Link]();

Collections implement IEnumerable, which means they can be used with foreach loops and LINQ queries. This makes it easy
to iterate through items, filter data, transform collections, and perform complex queries using consistent syntax across all
collection types. Modern C# development heavily relies on collections and LINQ to process data efficiently and expressively.

Performance Consideration: Dictionary lookups are O(1) constant time, while List searching is O(n) linear time. If
you frequently search for items by a unique identifier, use Dictionary instead of searching through a List repeatedly
—the performance difference becomes significant with thousands of items.
Practical Application: Student Management
System
Now that we've learned about C#.NET language features, file handling, collections, and error handling, let's combine these
concepts into a practical project. We'll build a simple student management system that demonstrates real-world application of
everything we've covered. This console application will store student records in memory using collections, save data to files
for persistence, handle user input with error checking, and provide a menu-driven interface.

This practical exercise reinforces how different C# concepts work together in a cohesive application. You'll see how List<T>
provides dynamic storage, file I/O enables data persistence across program runs, try-catch blocks create a robust user
experience, and string manipulation processes user input safely. Real applications layer these fundamental concepts to create
increasingly sophisticated functionality.

// Student Management System - Complete Example


using System;
using [Link];
using [Link];

namespace StudentManagement
{
// Student class with properties
class Student
{
public string StudentId { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public double GPA { get; set; }

public override string ToString()


{
return $"{StudentId},{Name},{Age},{GPA}";
}

public static Student FromString(string data)


{
string[] parts = [Link](',');
return new Student
{
StudentId = parts[0],
Name = parts[1],
Age = [Link](parts[2]),
GPA = [Link](parts[3])
};
}
}

class Program
{
static List students = new List();
static string dataFile = "[Link]";

static void Main(string[] args)


{
LoadStudents();

while (true)
{
[Link]("\n=== Student Management System ===");
[Link]("1. Add Student");
[Link]("2. View All Students");
[Link]("3. Search Student");
[Link]("4. Delete Student");
[Link]("5. Save and Exit");
[Link]("Choose option: ");

string choice = [Link]();

switch (choice)
{
case "1": AddStudent(); break;
case "2": ViewAllStudents(); break;
case "3": SearchStudent(); break;
case "4": DeleteStudent(); break;
case "5": SaveStudents(); return;
default: [Link]("Invalid option!"); break;
}
}
}

static void AddStudent()


{
try
{
[Link]("Enter Student ID: ");
string id = [Link]();

[Link]("Enter Name: ");


string name = [Link]();

[Link]("Enter Age: ");


int age = [Link]([Link]());

[Link]("Enter GPA: ");


double gpa = [Link]([Link]());

Student student = new Student


{
StudentId = id,
Name = name,
Age = age,
GPA = gpa
};

[Link](student);
[Link]("Student added successfully!");
}
catch (FormatException)
{
[Link]("Invalid input format!");
}
}

static void ViewAllStudents()


{
if ([Link] == 0)
{
[Link]("No students found.");
return;
}

[Link]("\n{0,-10} {1,-20} {2,-5} {3,-5}",


"ID", "Name", "Age", "GPA");
[Link](new string('-', 50));

foreach (Student s in students)


{
[Link]("{0,-10} {1,-20} {2,-5} {3,-5:F2}",
[Link], [Link], [Link], [Link]);
}
}

static void SearchStudent()


{
[Link]("Enter Student ID to search: ");
string id = [Link]();

Student found = [Link](s => [Link] == id);

if (found != null)
{
[Link]($"Found: {[Link]}, Age: {[Link]}, GPA: {[Link]}");
}
else
{
[Link]("Student not found.");
}
}

static void DeleteStudent()


{
[Link]("Enter Student ID to delete: ");
string id = [Link]();

int removed = [Link](s => [Link] == id);

if (removed > 0)
[Link]("Student deleted successfully!");
else
[Link]("Student not found.");
}

static void LoadStudents()


{
if ([Link](dataFile))
{
try
{
string[] lines = [Link](dataFile);
foreach (string line in lines)
{
if (![Link](line))
{
[Link]([Link](line));
}
}
[Link]($"Loaded {[Link]} students from file.");
}
catch (Exception ex)
{
[Link]($"Error loading data: {[Link]}");
}
}
}

static void SaveStudents()


{
try
{
using (StreamWriter writer = new StreamWriter(dataFile))
{
foreach (Student s in students)
{
[Link]([Link]());
}
}
[Link]("Data saved successfully!");
}
catch (Exception ex)
{
[Link]($"Error saving data: {[Link]}");
}
}
}
}

Data Model
1 Student class defines structure with properties and methods for serialization

In-Memory Storage
2 List<Student> holds active data during program execution

File Persistence
3 Load data on startup, save on exit using CSV format

User Interface
4 Menu-driven console interface with input validation

This example demonstrates professional programming practices: separating data (Student class) from logic (Program class),
using collections for flexible storage, implementing persistence with file I/O, handling errors gracefully, and providing clear
user feedback. As you build more complex applications, you'll apply these same patterns while adding layers like databases,
web interfaces, or graphical UIs.
Key Takeaways and Next Steps
Congratulations on completing this comprehensive exploration of C#.NET language features and the .NET framework! Let's
recap the key concepts you've learned and how they fit together to create robust applications. You now understand how to
create and organize .NET projects using namespaces and assemblies, work with C#'s type system including value and
reference types, manipulate strings efficiently, handle files and data persistence, manage collections of data dynamically, and
implement proper error handling and debugging practices.

Project Organization Type System Mastery


Use namespaces to organize code logically. Understand Choose appropriate data types for variables. Understand
assemblies as deployment units. Create modular, value vs reference semantics. Use type conversion
maintainable project structures. safely with proper error handling.

Data Management Professional Practices


Leverage collections instead of arrays for flexibility. Use Implement robust error handling with try-catch. Use
Dictionary for fast lookups. Apply appropriate collection debugging tools effectively. Write clean, readable code
types for specific scenarios. with proper conventions.

Common Pitfalls to Avoid Best Practices Checklist

Forgetting to close file streams (always use using) ✓ Follow naming conventions (PascalCase for classes,
Using string concatenation in loops (use StringBuilder) camelCase for variables)

Catching generic exceptions and swallowing errors ✓ Always use generic collections (List<T>, not
ArrayList)
Not checking for null references before use
✓ Handle exceptions at appropriate levels
Using non-generic collections in new code
✓ Use using statements with IDisposable resources
Hardcoding file paths instead of using [Link]()
✓ Validate user input before processing
✓ Comment complex logic, not obvious code

The foundation you've built with C#.NET language features prepares you for the next phase of your learning journey:
[Link] and database integration. You'll soon learn how to connect your C# applications to databases, execute SQL
queries, manage data connections efficiently, and build data-driven applications. All the concepts you've learned—collections
for storing query results, error handling for database exceptions, file I/O for import/export operations—will directly apply as
you work with databases.

Unit 2: C#.NET Basics Future Units


Language features, collections, file I/O - Current unit Web development, APIs, advanced frameworks -
completed Building on fundamentals

1 2 3

Unit 3: [Link]
Database connectivity, SQL integration, data
management - Coming next

Practice Recommendation: To solidify these concepts, build small projects that combine multiple topics. Create a
library management system (classes + collections + file I/O), a grade calculator (strings + math + error handling), or
a simple contact manager. Real practice makes these concepts second nature.

Keep experimenting, keep coding, and don't hesitate to revisit these notes as you build more complex applications. The
journey from understanding syntax to writing professional software is iterative—each project you build reinforces
fundamentals while introducing new challenges. Your next step into database programming will open exciting possibilities for
creating data-driven applications that store and retrieve information persistently, handle concurrent users, and scale to real-
world demands.

You might also like