C# Fundamentals for Java Developers

Table of Contents

  1. C# Language Basics 1.1 Syntax Overview 1.2 Variables and Data Types 1.3 Operators and Expressions 1.4 Control Flow Statements 1.5 Methods and Parameters

C# Language Basics

Syntax Overview

If you're coming from a Java background, you'll find C# surprisingly familiar yet distinctive. Both languages share a C-style syntax, but C# introduces several unique constructts worth exploring.

Program Structure

A basic C# application follows this pattern:

using System;

namespace MyApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Greetings, World!");
        }
    }
}

Comparing to the equivalent Java implementation:

public class MyApplication {
    public static void main(String[] args) {
        System.out.println("Greetings, World!");
    }
}

Key differences emerge in several areas. C# employs using directives for namespace imports rather then Java's import statement. The entry point signature differs slightly: C# uses static void Main(string[] args) while Java requires public static void main(String[] args). Output operations also vary, with C# favoring Console.WriteLine() against Java's System.out.println().

Modern C# (version 9 and later) supports top-level statements, eliminating the need for explicit namespaces and class declarations:

Console.WriteLine("Greetings, World!");

Naming Conventions

C# and Java follow different naming conventions:

Element C# Convention Java Convention
Methods, Properties PascalCase camelCase
Local Variables, Parameters camelCase camelCase
Interfaces Prefix with "I" No special prefix
Constants PascalCase UPPER_SNAKE_CASE

Primitive Data Types

Both languages share common numeric types, though C# provides additional unsigned variants:

// C# primitives
sbyte signedByte = -128;        // 8-bit signed
byte unsignedByte = 255;       // 8-bit unsigned
short shortVal = 32000;        // 16-bit
ushort uShortVal = 65000;      // 16-bit unsigned
int count = 42;                // 32-bit signed
uint positiveCount = 100u;    // 32-bit unsigned
long largeNumber = 999999L;    // 64-bit signed
ulong bigPositive = 999999UL;  // 64-bit unsigned
float pi = 3.14f;             // 32-bit floating point
double precision = 3.14159;   // 64-bit floating point
decimal currency = 99.99m;     // High-precision decimal
bool flag = true;             // Boolean
char letter = 'A';            // 16-bit character
string text = "Hello";       // Reference type string

Arrays

Array declaration syntax differs slightly between the two languages:

// C# array syntax
int[] scores = new int[5];
string[] names = { "Emma", "James", "Sophia" };
// Java array syntax
int[] scores = new int[5];
String[] names = { "Emma", "James", "Sophia" };

Control Structures

Control flow constructs remain nearly identical between the languages:

// Conditional statements
if (temperature > 30)
{
    Console.WriteLine("Hot day");
}
else if (temperature > 20)
{
    Console.WriteLine("Pleasant weather");
}
else
{
    Console.WriteLine("Cold day");
}

// Iteration constructs
for (int i = 0; i < 10; i++)
{
    Console.WriteLine($"Iteration {i}");
}

while (keepRunning)
{
    // Process loop
}

// Pattern matching switch
switch (dayOfWeek)
{
    case DayOfWeek.Monday:
    case DayOfWeek.Tuesday:
    case DayOfWeek.Wednesday:
        Console.WriteLine("Mid-week");
        break;
    default:
        Console.WriteLine("Other day");
        break;
}

Exception Handling

Exception handling follows identical patterns:

try
{
    ProcessFile("data.txt");
}
catch (FileNotFoundException fnf)
{
    Console.WriteLine($"File missing: {fnf.Message}");
}
catch (Exception ex)
{
    Console.WriteLine($"Unexpected error: {ex.Message}");
}
finally
{
    ReleaseResources();
}

Comments

All three comment styles work identically:

// Single-line comment

/* Multi-line
   comment block */

/// <summary>
/// XML documentation comment (similar to Javadoc)
/// </summary>

Variables and Data Types

Understanding C#'s type system helps transition smoothly from Java.

Variable Declaration

int age = 25;
string fullName = "Alice";
bool isActive = true;
int age = 25;
String fullName = "Alice";
boolean isActive = true;

Note that C# uses lowercase string and bool, while Java requires uppercase String and boolean.

Value Types vs Reference Types

C# maintains a clearer distinction between value and reference types:

Value Types (stored directly on the stack):

  • Primitives: int, float, double, bool, char
  • Custom struct types
  • Enumerations

Reference Types (stored on the heap, with reference on stack):

  • Classes
  • Interfaces
  • Delegates
  • Arrays
  • Strings (immutable reference type with value semantics)

Nullable Types

C# introduces nullable value types, allowing primitives to hold null:

int? optionalCount = null;
bool? isValid = null;

// Checking nullable values
if (optionalCount.HasValue)
{
    int value = optionalCount.Value;
}

This proves invaluable when working with databases or optional user input.

Implicit Typing with var

var productCount = 150;        // Inferred as int
var customerName = "Acme";    // Inferred as string
var isComplete = false;       // Inferred as bool

Java introduced var in version 10, but with more restrictions on usage contexts.

Constants

C# uses const for compile-time constants:

const int MaxRetry = 3;
const string DefaultLocale = "en-US";

Java prefers final for this purpose:

final int MAX_RETRY = 3;
final String DEFAULT_LOCALE = "en-US";

Enumerations

C# enums offer more flexibility with explicit underlying types:

public enum Priority : byte
{
    Low = 0,
    Medium = 5,
    High = 10
}

public enum Status
{
    Pending,
    Processing,
    Completed
}

Java enum are more object-oriented, essentially becoming special classes:

public enum Priority {
    LOW(0),
    MEDIUM(5),
    HIGH(10);
    
    private final int value;
    
    Priority(int value) {
        this.value = value;
    }
}

Type Conversions

C# provides multiple conversion mechanisms:

// Implicit conversion (safe narrowing)
int smallNumber = 42;
long largeNumber = smallNumber;

// Explicit conversion (casting)
double precise = 3.14159;
int truncated = (int)precise;

// Using Convert class
string numeric = "123";
int converted = Convert.ToInt32(numeric);

// Parse method
double parsed = double.Parse("2.718");

// Safe TryParse pattern
string input = "999";
if (int.TryParse(input, out int result))
{
    Console.WriteLine($"Parsed: {result}");
}
else
{
    Console.WriteLine("Invalid number");
}

Operators and Expressions

Most operators work identically, but C# includes several unique additions.

Arithmetic Operators

int a = 17, b = 5;
int sum = a + b;           // 22
int difference = a - b;    // 12
int product = a * b;       // 85
int quotient = a / b;       // 3 (integer division)
int remainder = a % b;     // 2

Assignment Operators

int x = 10;
x += 4;      // 14
x -= 3;      // 11
x *= 2;      // 22

// Null-coalescing assignment (C# 8.0+)
string username = null;
username ??= "Anonymous";  // Assigns "Anonymous" if null

Comparison and Logical Operators

bool isAdult = age >= 18;
bool canVote = isCitizen && hasRegistered;
bool isWeekend = day == Day.Saturday || day == Day.Sunday;

Bitwise Operations

int flags = 0b1100;     // Binary 12
int mask = 0b1010;      // Binary 10

int andResult = flags & mask;   // 8 (1000)
int orResult = flags | mask;    // 14 (1110)
int xorResult = flags ^ mask;   // 6 (0110)
int shifted = flags << 1;       // 24 (11000)

Null-Coalescing Operators

// ?? returns the left operand unless it's null
string config = null;
string setting = config ?? "default";  // "default"

// ?. safe member access
Person person = null;
int? length = person?.Name?.Length;    // null (no exception)

Pattern Matching

C# 7.0+ introduced enhanced pattern matching:

object data = 42;

if (data is int number)
{
    Console.WriteLine($"Integer value: {number}");
}

// Switch pattern matching
switch (shape)
{
    case Circle c when c.Radius > 10:
        area = Math.PI * c.Radius * c.Radius;
        break;
    case Rectangle r:
        area = r.Width * r.Height;
        break;
    default:
        area = 0;
        break;
}

Switch Expressions (C# 8.0+)

string GetDayType(int day) => day switch
{
    1 or 2 or 3 => "Early week",
    4 or 5 => "Midweek",
    _ => "Weekend"
};

Lambda Expressions

// Func<T, TResult> for transformations
Func<int, int> square = x => x * x;
int squared = square(7);  // 49

// Action<T> for void operations
Action<string> printer = msg => Console.WriteLine(msg);

// Complex lambda with multiple parameters
Func<int, int, string> formatter = (a, b) => $"{a} + {b} = {a + b}";

Expression-bodied Members

Simplify single-expression members:

public class Rectangle
{
    public double Width { get; set; }
    public double Height { get; set; }
    
    public double Area => Width * Height;
    public double Perimeter => 2 * (Width + Height);
    
    public double CalculateDiagonal() => Math.Sqrt(Width * Width + Height * Height);
}

String Interpolation

C# string interpolation offers readable formatting:

string name = "Marcus";
int score = 95;
string report = $"Player {name} achieved {score} points.";

// Complex expressions
double radius = 5;
string circleInfo = $"Circumference: {2 * Math.PI * radius:F2}";

Control Flow Statements

Both languages share similar control structures, but C# adds modern pattern matching capabilities.

Pattern Matching with if

object response = FetchResponse();

if (response is string message && message.Length > 0)
{
    Console.WriteLine($"Message received: {message}");
}
else if (response is int code)
{
    Console.WriteLine($"Status code: {code}");
}

Advanced switch Features

// Range pattern matching
switch (temperature)
{
    case < 0:
        Console.WriteLine("Freezing");
        break;
    case >= 0 and < 20:
        Console.WriteLine("Cold");
        break;
    case >= 20 and < 30:
        Console.WriteLine("Comfortable");
        break;
    default:
        Console.WriteLine("Hot");
        break;
}

Loop Variations

// Traditional for loop
for (int i = 0; i < items.Count; i++)
{
    Process(items[i]);
}

// foreach with LINQ filtering
foreach (var item in items.Where(i => i.IsActive))
{
    Process(item);
}

// Collection initialization
var primes = new List<int> { 2, 3, 5, 7, 11 };

Exception Filters

try
{
    ProcessOrder(order);
}
catch (OrderException ex) when (ex.Severity == ErrorSeverity.Critical)
{
    NotifyAdmin(ex);
    LogCritical(ex);
}
catch (OrderException ex) when (ex.Severity == ErrorSeverity.Warning)
{
    LogWarning(ex);
}

Resource Management with using

Traditional using statement:

using (var reader = new StreamReader("data.csv"))
{
    var content = reader.ReadToEnd();
    Process(content);
}

Modern using declaration (C# 8.0+):

using var reader = new StreamReader("data.csv");
var content = reader.ReadToEnd();
// Resource automatically disposed at end of scope

Methods and Parameters

Basic Method Definition

public int CalculateTotal(int quantity, decimal unitPrice)
{
    return quantity * (int)unitPrice;
}

Parameter Modifiers

C# provides multiple parameter passing strategies:

public void ModifyValue(int value)
{
    value = 100;  // Changes local copy only
}

public void ModifyRef(ref int value)
{
    value = 100;  // Changes caller's variable
}

public void RetrieveResult(int input, out int result)
{
    result = input * 2;  // Provides output value
}

public void SumAll(params int[] values)
{
    Console.WriteLine($"Received {values.Length} arguments");
}

// Usage
int original = 50;
ModifyRef(ref original);
Console.WriteLine(original);  // 100

int output;
RetrieveResult(21, out output);  // output = 42

SumAll(1, 2, 3, 4, 5);  // 5 arguments

Optional and Named Parameters

public void SendEmail(
    string recipient,
    string subject = "No Subject",
    bool isUrgent = false)
{
    Console.WriteLine($"To: {recipient}, Subject: {subject}");
}

// Various call patterns
SendEmail("user@example.com");
SendEmail("user@example.com", "Meeting Tomorrow");
SendEmail("user@example.com", isUrgent: true);
SendEmail(recipient: "user@example.com", subject: "Update");

Local Functions

Encapsulate helper logic within methods:

public int ComputeFactorial(int n)
{
    int FactorialHelper(int current, int accumulator)
    {
        return current == 0 ? accumulator : 
            FactorialHelper(current - 1, current * accumulator);
    }
    
    return n < 0 ? 1 : FactorialHelper(n, 1);
}

Async Methods

public async Task<string> FetchUserDataAsync(int userId)
{
    using var client = new HttpClient();
    var response = await client.GetStringAsync(
        $"https://api.example.com/users/{userId}");
    return response;
}

// Invocation
string data = await FetchUserDataAsync(123);

Extension Methods

Add functionality to existing types without inheritance:

public static class StringExtensions
{
    public static bool IsValidEmail(this string input)
    {
        return input.Contains('@') && input.Contains('.');
    }
    
    public static string Truncate(this string input, int maxLength)
    {
        return input.Length <= maxLength ? 
            input : input.Substring(0, maxLength) + "...";
    }
}

// Usage
string email = "user@domain.com";
bool valid = email.IsValidEmail();
string shortened = "Long text content".Truncate(10);

Generic Methods

public T FindMaximum<T>(T first, T second) where T : IComparable<T>
{
    return first.CompareTo(second) > 0 ? first : second;
}

// Usage
int maxInt = FindMaximum(42, 17);
string maxStr = FindMaximum("apple", "banana");

Method Composition

Functional programming support through delegate composition:

Func<int, int> doubleValue = x => x * 2;
Func<int, int> addFive = x => x + 5;

// Compose operations
Func<int, int> pipeline = x => addFive(doubleValue(x));
int result = pipeline(10);  // (10 * 2) + 5 = 25

// Or using built-in composition methods
Func<int, int> composed = doubleValue.Compose(addFive);

Tags: C# java .NET programming Language Comparison

Posted on Sat, 03 Oct 2026 17:00:41 +0000 by assonitis