C# and ASP.NET Core Concepts: Essential Development Topics

C# Fundamentals

CLR and .NET Applied Technologies

The Common Language Runtime (CLR) is the heart of the .NET framework, providing the execution environment for .NET applications.

CLR (Common Language Runtime)

It is the execution engine for .NET applications. It manages memory, code execution, security, and exception handling, among other services.

Features of CLR

  • Memory Management: Automatic allocation and deallocation of memory via garbage collection, preventing memory leaks.
  • Code Access Security (CAS): Controls access to system resources based on the identity of the code.
  • Exception Handling: Provides a unified model for detecting and recovering from errors during program execution.
  • Cross-language Integration: Enables different .NET languages (e.g., C#, VB.NET) to work together seamlessly.
  • Just-In-Time (JIT) Compilation: Converts Intermediate Language (IL) code into native machine code at runtime, optimizing performance.

Four Applied Technologies in .NET

The .NET ecosystem supports various application development types:

  • ASP.NET: For building dynamic web applications and services.
  • ADO.NET: Provides data access services for connecting to and manipulating databases.
  • Windows Forms (WinForms): A framework for creating traditional desktop GUI applications for Windows.
  • WPF (Windows Presentation Foundation): A newer, richer UI framework for desktop applications, utilizing XAML for declarative UI design.

C# Type-Safety and .NET Technologies

C# is designed as a type-safe language, which contributes significantly to robust and reliable software development.

Type-Safety in C#

C# is considered type-safe because it enforces strict type checking at both compile-time and runtime. This means:

  • You cannot use variables without declaring their types.
  • Implicit conversions between incompatible types are generally disallowed, preventing unexpected data loss or errors.
  • It helps prevent invalid type conversions and memory-related errors, leading to more stable applications.

(For more on .NET technologies, refer to the “CLR and .NET Applied Technologies” section.)

C# Value Types vs. Reference Types

Understanding the distinction between value types and reference types is fundamental to C# programming, impacting memory management and behavior.

Value Type

  • Stores actual data directly in its memory location.
  • Typically stored on the stack.
  • Examples: int, float, double, struct, enum.

Reference Type

  • Stores a reference (memory address) to the data, which is stored elsewhere.
  • Typically stored on the heap.
  • Examples: class, string, array, interface, delegate.

Example

int a = 5; // Value Type: 'a' directly holds the value 5
string name = "Lalit"; // Reference Type: 'name' holds a reference to the string "Lalit" on the heap

C# Contextual Keywords and Jagged Arrays

Contextual keywords add flexibility to the language, while jagged arrays offer dynamic multi-dimensional data structures.

Five Contextual Keywords in C#

Contextual keywords are special words that have meaning only in a specific code context, allowing them to be used as identifiers elsewhere:

  • var: Used for implicitly typed local variables.
  • yield: Used in iterator blocks to provide a value to the enumerator object.
  • async: Marks a method as asynchronous, enabling the use of await.
  • await: Pauses the execution of an async method until an awaited task completes.
  • partial: Allows splitting the definition of a class, struct, or interface across multiple source files.

Jagged Array Program

A jagged array is an array of arrays, where each inner array can be of a different size.

using System;

class Program
{
    static void Main()
    {
        int[][] arr = new int[3][]; // Declare a jagged array with 3 rows

        arr[0] = new int[] { 10, 20 }; // First row has 2 elements
        arr[1] = new int[] { 5, 15, 25 }; // Second row has 3 elements
        arr[2] = new int[] { 7 }; // Third row has 1 element

        for (int i = 0; i < arr.Length; i++)
        {
            int sum = 0;
            Console.Write($"Row {i + 1}: ");
            foreach (int val in arr[i])
            {
                Console.Write(val + " ");
                sum += val;
            }
            Console.WriteLine($"=> Sum: {sum}");
        }
    }
}

C# String Interpolation and Parameter Passing

These features enhance code readability and control how data is handled when passed to methods.

String Interpolation

Introduced in C# 6.0, string interpolation provides a concise and readable syntax for creating formatted strings by embedding expressions directly within string literals, prefixed with $.

string name = "Lalit";
Console.WriteLine($"Hello {name}"); // Output: Hello Lalit
int age = 30;
Console.WriteLine($"Name: {name}, Age: {age}"); // Output: Name: Lalit, Age: 30

Value vs. Reference Passing

C# supports different mechanisms for passing arguments to methods, affecting whether the original variable is modified.

using System;

class Program
{
    // Method to demonstrate value passing
    static void ModifyValue(int a)
    {
        a = 20; // Changes only the local copy of 'a'
    }

    // Method to demonstrate reference passing using 'ref' keyword
    static void ModifyRef(ref int a)
    {
        a = 20; // Changes the original variable 'a'
    }

    static void Main()
    {
        int x = 10;
        Console.WriteLine($"Before ModifyValue: x = {x}"); // Output: 10
        ModifyValue(x);
        Console.WriteLine($"After ModifyValue: x = {x}");  // Output: 10 (x remains unchanged)

        Console.WriteLine($"\nBefore ModifyRef: x = {x}"); // Output: 10
        ModifyRef(ref x);
        Console.WriteLine($"After ModifyRef: x = {x}");    // Output: 20 (x is changed)
    }
}

C# Namespace Usage and Organization

Namespaces are fundamental for organizing code and preventing naming conflicts in larger C# projects.

Why Use Namespaces?

  • To Organize Code: Group related classes, structs, interfaces, enums, and delegates logically.
  • To Avoid Name Conflicts: Prevent ambiguity when different libraries or parts of an application define types with the same name.

Creating and Using a Namespace

using System; // Using directive to import the System namespace

namespace MyProject // Declaring a namespace
{
    class Test
    {
        public void Greet() => Console.WriteLine("Hello from MyProject!");
    }
}

class Program
{
    static void Main()
    {
        // Accessing the Test class using its fully qualified name
        MyProject.Test t = new MyProject.Test();
        t.Greet();

        // Alternatively, using a 'using' directive for MyProject:
        // using MyProject;
        // Test t2 = new Test();
        // t2.Greet();
    }
}

C# Object-Oriented Programming

C# Indexers, Properties, and Generic Classes

Indexer vs. Properties: Key Differences

FeatureIndexerProperty
Syntaxthis[index]get/set
PurposeAccess data like an arrayAccess data like a variable
Usageobj[0]obj.Name

Generic Class Example

Generic classes allow you to define classes with type parameters, enabling reusable code that works with different data types.

class MyBox<T>
{
    public T Value;
    public void Show() => Console.WriteLine(Value);
}

class Program
{
    static void Main()
    {
        MyBox<int> b1 = new MyBox<int> { Value = 100 };
        MyBox<string> b2 = new MyBox<string> { Value = "Hello" };
        b1.Show();
        b2.Show();
    }
}

C# Delegates and Operator Overloading

Delegates provide a way to treat methods as objects, while operator overloading allows custom behavior for operators.

Types of Delegates

Delegates are type-safe function pointers. Key types include:

  • Single-cast Delegate: Points to a single method.
  • Multi-cast Delegate: Can point to multiple methods, which are invoked sequentially.
  • Generic Delegate (Func, Action, Predicate): Predefined delegates for common scenarios, reducing the need for custom delegate declarations.

Time Class Program: Operator Overloading Example

using System;

class Time
{
    public int Hours, Minutes, Seconds;

    public Time(int h, int m, int s)
    {
        Hours = h; Minutes = m; Seconds = s;
    }

    public void DisplayTime() => Console.WriteLine($"{Hours:D2}:{Minutes:D2}:{Seconds:D2}");

    public static Time operator +(Time t1, Time t2)
    {
        int s = t1.Seconds + t2.Seconds;
        int m = t1.Minutes + t2.Minutes + s / 60;
        int h = t1.Hours + t2.Hours + m / 60;
        return new Time(h % 24, m % 60, s % 60);
    }

    public static bool operator >(Time t1, Time t2)
    {
        if (t1.Hours != t2.Hours) return t1.Hours > t2.Hours;
        if (t1.Minutes != t2.Minutes) return t1.Minutes > t2.Minutes;
        return t1.Seconds > t2.Seconds;
    }

    public static bool operator <(Time t1, Time t2)
    {
        if (t1.Hours != t2.Hours) return t1.Hours < t2.Hours;
        if (t1.Minutes != t2.Minutes) return t1.Minutes < t2.Minutes;
        return t1.Seconds < t2.Seconds;
    }
}

C# Operator Overloading: Binary and Relational

Operator overloading enables you to define how standard operators behave when applied to instances of your custom classes.

Operator Overloading Definition

Allows defining custom behavior for operators when used with user-defined types, enhancing readability and natural syntax for complex objects.

Program: Point Class Example

using System;

class Point
{
    public int X, Y;

    public Point(int x, int y) { X = x; Y = y; }

    public static Point operator +(Point a, Point b)
    {
        return new Point(a.X + b.X, a.Y + b.Y);
    }

    public static bool operator >(Point a, Point b)
    {
        // Example: Compare based on sum of coordinates
        return (a.X + a.Y) > (b.X + b.Y);
    }

    public static bool operator <(Point a, Point b)
    {
        // Example: Compare based on sum of coordinates
        return (a.X + a.Y) < (b.X + b.Y);
    }

    public void Display() => Console.WriteLine($"({X}, {Y})");
}

class Program
{
    static void Main()
    {
        Point p1 = new Point(2, 3);
        Point p2 = new Point(1, 5);
        Point sum = p1 + p2;

        sum.Display(); // Output: (3, 8)

        Console.WriteLine(p1 > p2 ? "p1 is greater" : "p2 is greater"); // (2+3=5) > (1+5=6) is false, so "p2 is greater"
    }
}

C# Interfaces and Multiple Inheritance

Interfaces are a cornerstone of C# OOP, enabling polymorphism and a form of multiple inheritance for behavior definition.

Interface

A contract that defines a set of members (methods, properties, events, indexers) that a class or struct must implement. Key characteristics:

  • Contains only method/property declarations (no implementation in older C# versions; C# 8.0+ allows default implementations).
  • Cannot be instantiated directly.

Multiple Inheritance via Interface

C# does not support multiple inheritance of implementation from multiple base classes. However, it supports multiple inheritance of type by allowing a class to implement multiple interfaces, thereby inheriting multiple contracts.

Example

using System;

interface IA { void Show(); }
interface IB { void Display(); }

class Demo : IA, IB
{
    public void Show() => Console.WriteLine("From IA");
    public void Display() => Console.WriteLine("From IB");
}

class Program
{
    static void Main()
    {
        Demo d = new Demo();
        d.Show();
        d.Display();
    }
}

C# Abstract Classes and the base Keyword

Abstract classes provide a blueprint for derived classes, while the base keyword facilitates interaction with the parent class.

Uses of the base Keyword

The base keyword is used to access members of the base class from within a derived class:

  • Access Base Class Members: Call methods, properties, or fields defined in the base class that might be hidden or overridden in the derived class.
  • Call Base Class Constructor: Invoke a constructor of the base class from a derived class’s constructor, ensuring proper initialization of the base part of the object.

Program with Abstract Class

An abstract class cannot be instantiated and may contain abstract members (methods, properties) that must be implemented by non-abstract derived classes.

using System;

abstract class Shape // Abstract class
{
    public abstract void Draw(); // Abstract method (no implementation)
}

class Circle : Shape // Derived class implementing the abstract method
{
    public override void Draw()
    {
        Console.WriteLine("Drawing Circle");
    }
}

class Program
{
    static void Main()
    {
        Shape s = new Circle(); // Cannot instantiate Shape directly, but can use a derived class
        s.Draw(); // Output: Drawing Circle
    }
}

C# Virtual Methods and Multi-Level Inheritance

Virtual methods enable polymorphism, allowing derived classes to provide specific implementations, often seen in inheritance hierarchies.

Virtual Method

A method declared in a base class using the virtual keyword. It can be overridden in derived classes using the override keyword, allowing for polymorphic behavior at runtime.

Program: Multi-Level Inheritance Example

using System;

class Animal
{
    public virtual void Speak() => Console.WriteLine("Animal speaks");
}

class Dog : Animal // Dog inherits from Animal
{
    public override void Speak() => Console.WriteLine("Dog barks");
}

class Puppy : Dog // Puppy inherits from Dog (multi-level inheritance)
{
    public override void Speak() => Console.WriteLine("Puppy yaps");
}

class Program
{
    static void Main()
    {
        Animal a = new Puppy(); // Polymorphism: An Animal reference pointing to a Puppy object
        a.Speak(); // Output: Puppy yaps (runtime binding to Puppy's Speak method)
    }
}

C# Static Classes and Constructors

Static members and classes are fundamental concepts for utility classes and shared data in C#.

Static Class

Characteristics of a static class:

  • Cannot be instantiated (you cannot create objects of a static class).
  • Contains only static members (fields, methods, properties, events).
  • Sealed implicitly (cannot be inherited).

Static Constructor

Key aspects of a static constructor:

  • Initializes static data or performs actions exactly once when the class is first loaded into memory.
  • Runs automatically before any static members are accessed or any instance of the class is created.
  • Cannot take access modifiers or parameters.

Example: Logger Static Class

using System;

static class Logger
{
    static int count;

    static Logger()
    {
        count = 0;
        Console.WriteLine("Static Constructor Called");
    }

    public static void Log(string msg)
    {
        count++;
        Console.WriteLine($"{count}: {msg}");
    }
}

class Program
{
    static void Main()
    {
        Logger.Log("Program started");
        Logger.Log("Program running");
    }
}

C# Exception Handling

C# Exception Handling: System vs. Application Exceptions

Understanding the difference between system and application exceptions is crucial for robust error handling in C# applications.

System Exception

Errors generated by the .NET runtime, such as NullReferenceException or IndexOutOfRangeException. These typically indicate fundamental issues within the application or environment.

Program with Application Exception

Application exceptions are custom exceptions or instances of ApplicationException that are thrown by the application code to signal specific business logic errors.

using System;

class Program
{
    static void Main()
    {
        Console.Write("Enter Balance: ");
        double balance = Convert.ToDouble(Console.ReadLine());

        Console.Write("Enter Withdraw Amount: ");
        double withdraw = Convert.ToDouble(Console.ReadLine());

        try
        {
            if (withdraw > balance)
                throw new ApplicationException("Insufficient Balance!");
            else
                Console.WriteLine($"Remaining Balance: {balance - withdraw}");
        }
        catch (ApplicationException ex)
        {
            Console.WriteLine("Error: " + ex.Message);
        }
    }
}

C# Exception Handling: Common Scenarios

An exception is an event that disrupts the normal flow of program execution. C# provides structured exception handling mechanisms to manage these errors gracefully.

Program: Divide by Zero and Index Out of Range

using System;

class Program
{
    static void Main()
    {
        try
        {
            int a = 10, b = 0;
            Console.WriteLine(a / b); // This will throw DivideByZeroException

            int[] arr = { 1, 2, 3 };
            Console.WriteLine(arr[5]); // This will throw IndexOutOfRangeException
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("Divide By Zero Error: " + ex.Message);
        }
        catch (IndexOutOfRangeException ex)
        {
            Console.WriteLine("Index Error: " + ex.Message);
        }
    }
}

LINQ (Language Integrated Query)

LINQ Operators and Data Manipulation

Language Integrated Query (LINQ) provides a powerful way to query data from various sources using a consistent syntax.

Five Standard LINQ Operators

Commonly used LINQ operators include:

  • Where: Filters a sequence of values based on a predicate.
  • Select: Projects each element of a sequence into a new form.
  • OrderByDescending: Sorts the elements of a sequence in descending order.
  • Sum: Calculates the sum of a sequence of numeric values.
  • Average: Calculates the average of a sequence of numeric values.

LINQ Program: Employee Salary Example

using System;
using System.Linq;

class Employee
{
    public string Name;
    public double Salary;
}

class Program
{
    static void Main()
    {
        Employee[] employees = {
            new Employee { Name = "A", Salary = 5000 },
            new Employee { Name = "B", Salary = 6000 },
            new Employee { Name = "C", Salary = 5500 },
            new Employee { Name = "D", Salary = 4800 },
            new Employee { Name = "E", Salary = 7000 }
        };

        var totalSalary = employees.Sum(e => e.Salary);
        Console.WriteLine($"Total Salary: {totalSalary}");

        var sorted = employees.OrderByDescending(e => e.Salary);
        foreach (var emp in sorted)
            Console.WriteLine($"{emp.Name}: {emp.Salary}");
    }
}

LINQ Query Expressions: Filtering Data

LINQ query expressions offer a SQL-like syntax for querying collections in C#, making data retrieval intuitive.

Query Expression Definition

A concise way to query collections using LINQ syntax, often preferred for its readability when dealing with complex queries.

Program: Filter Students Example

using System;
using System.Linq;
using System.Collections.Generic;

class Student
{
    public string Name, Address, Campus;
}

class Program
{
    static void Main()
    {
        List<Student> students = new List<Student>
        {
            new Student { Name="Rita", Address="Kirtipur", Campus="Patan Multiple Campus" },
            new Student { Name="Sita", Address="Bhaktapur", Campus="Patan Multiple Campus" },
            new Student { Name="Gita", Address="Kirtipur", Campus="TU" }
        };

        var result = from s in students
                     where s.Address == "Kirtipur" && s.Campus == "Patan Multiple Campus"
                     select s;

        foreach (var student in result)
            Console.WriteLine(student.Name);
    }
}

ADO.NET and Database Operations

ADO.NET: ExecuteScalar vs. ExecuteReader

These methods are fundamental for interacting with databases using ADO.NET, each serving a distinct purpose.

Method Differences

MethodPurpose
ExecuteScalar()Returns a single scalar value (e.g., COUNT, SUM, MAX) from the first row and first column of the result set.
ExecuteReader()Reads multiple rows and columns of data using a DataReader object, providing forward-only, read-only access.

Bank Database Program: Insert and Display Example

using System;
using MySql.Data.MySqlClient;

class Program
{
    static void Main()
    {
        string connStr = "server=localhost;user=root;database=Bank;password=yourpass;";
        using (MySqlConnection conn = new MySqlConnection(connStr))
        {
            conn.Open();

            // Insert data
            for (int i = 1; i <= 5; i++)
            {
                string insert = "INSERT INTO Customer (Account_no, Name, Address, Balance) " +
                                "VALUES (@Acc, @Name, @Addr, @Bal)";
                var cmd = new MySqlCommand(insert, conn);
                cmd.Parameters.AddWithValue("@Acc", 100 + i);
                cmd.Parameters.AddWithValue("@Name", $"User{i}");
                cmd.Parameters.AddWithValue("@Addr", "Kathmandu");
                cmd.Parameters.AddWithValue("@Bal", 1000 * i + 2000);
                cmd.ExecuteNonQuery();
            }
            Console.WriteLine("5 customers inserted.");

            // Display data using ExecuteReader
            string query = "SELECT Account_no, Name, Balance FROM Customer WHERE Balance > 5000";
            var reader = new MySqlCommand(query, conn).ExecuteReader();
            Console.WriteLine("\nCustomers with Balance > 5000:");
            while (reader.Read())
            {
                Console.WriteLine($"{reader["Account_no"]}, {reader["Name"]}, {reader["Balance"]}");
            }
            reader.Close();
        }
    }
}

ADO.NET: ExecuteReader vs. ExecuteNonQuery

These two ADO.NET methods are essential for different types of database operations: reading data versus modifying it.

Method Differences

FeatureExecuteReader()ExecuteNonQuery()
PurposeRead data (e.g., SELECT statements)Modify data (e.g., INSERT, UPDATE, DELETE, CREATE TABLE)
Return TypeDataReader object (e.g., MySqlDataReader)int (representing the number of rows affected by the operation)

Program: MySQL Product Table Example

using System;
using MySql.Data.MySqlClient;

class Program
{
    static void Main()
    {
        string connStr = "server=localhost;user=root;database=Company;password=yourpass;";
        using (MySqlConnection conn = new MySqlConnection(connStr))
        {
            conn.Open();

            // Read data using ExecuteReader
            string query = "SELECT ProductId, ProductName, UnitPrice FROM Product WHERE UnitPrice > 1000";
            MySqlCommand cmd = new MySqlCommand(query, conn);
            MySqlDataReader reader = cmd.ExecuteReader();
            Console.WriteLine("Products with UnitPrice > 1000:");
            while (reader.Read())
            {
                Console.WriteLine($"{reader["ProductId"]} - {reader["ProductName"]} - {reader["UnitPrice"]}");
            }
            reader.Close();

            // Update data using ExecuteNonQuery
            string update = "UPDATE Product SET UnitPrice = 4500 WHERE ProductId = 50";
            MySqlCommand updateCmd = new MySqlCommand(update, conn);
            int rows = updateCmd.ExecuteNonQuery();
            Console.WriteLine($"{rows} row(s) updated.");
        }
    }
}

ASP.NET Web Forms

ASP.NET Simple Interest Calculator Form

This example demonstrates basic form navigation in ASP.NET using two pages: one for input and another for displaying the result.

Page 1: Input Form (InterestForm.aspx)

<form method="post" action="Result.aspx">
    Principal: <input type="text" name="principal" /><br />
    Rate: <input type="text" name="rate" /><br />
    Time: <input type="text" name="time" /><br />
    <input type="submit" value="Calculate Interest" />
</form>

Page 2: Display Result (Result.aspx)

<%
    double p = double.Parse(Request.Form["principal"]);
    double r = double.Parse(Request.Form["rate"]);
    double t = double.Parse(Request.Form["time"]);
    double si = (p * r * t) / 100;
    Response.Write($"Simple Interest: {si}");
%>

ASP.NET Web Server Controls and Forms

ASP.NET Web Server Controls provide an object-oriented way to create user interfaces for web applications, abstracting away much of the HTML and client-side scripting.

Common Web Server Controls

  • TextBox: For single-line or multi-line text input.
  • Button: To trigger server-side events.
  • DropDownList: For selecting an item from a predefined list.
  • RadioButton: For selecting a single option from a group.
  • Label: To display static text.

Page 1: Student Registration Form (StudentForm.aspx)

<%@ Page Language="C#" AutoEventWireup="true" %>
<html>
<body>
    <form runat="server" method="post" action="StudentResult.aspx">
        Name: <asp:TextBox ID="txtName" runat="server" /><br />
        Age: <asp:TextBox ID="txtAge" runat="server" /><br />
        Gender:
        <asp:RadioButton ID="rbMale" GroupName="gender" Text="Male" runat="server" />
        <asp:RadioButton ID="rbFemale" GroupName="gender" Text="Female" runat="server" /><br />
        <asp:Button ID="btnSubmit" Text="Submit" runat="server" PostBackUrl="StudentResult.aspx" />
    </form>
</body>
</html>

Page 2: Display Student Result (StudentResult.aspx)

<%@ Page Language="C#" AutoEventWireup="true" %>
<html>
<body>
<%
    string name = Request.Form["txtName"];
    string age = Request.Form["txtAge"];
    // Determine gender based on which radio button was checked
    string gender = Request.Form["rbMale"] != null ? "Male" : (Request.Form["rbFemale"] != null ? "Female" : "Not Specified");

    Response.Write($"Name: {name}<br/>Age: {age}<br/>Gender: {gender}");
%>
</body>
</html>