Basic C# Syntax: Structure, Examples & Your First App

Basic C# Syntax: Build Your First Program Without Guesswork

Are you sure “Hello, World!” is trivial? Most beginners (and a few seniors) trip over at least three basics: where Main lives, what a namespace really is, and why fields ≠ properties. In this post, you’ll ship a tiny console app and finally connect the dots between using directives, namespaces, classes, constructors, methods, and the entry point.

Why this matters

If C# were a city, using directives are street signs, namespaces are neighborhoods, classes are buildings, fields/properties are rooms, methods are the doors, and constructors are the keys. Once you see the map, everything else is just… taking a walk.

Structure of a C# Program

At its core, a C# program is a set of source files compiled into an assembly (EXE or DLL). A minimal, modern console app can be as short as one file with top‑level statements – or the classic structure with an explicit Main method inside a class.

// File: Program.cs (modern: top‑level statements)
using System;

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

Or the classic form:

// File: Program.cs (classic entry point)
using System;

namespace GettingStarted
{
    internal static class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

When to choose which?

  • Top‑level statements: perfect for demos, small utilities, or minimal APIs.
  • Explicit Main in a class: clearer for larger apps, multiple entry points, or when you want full control over startup.

Tip: In SDK-style projects, implicit usings may be on by default. That’s why your one-file app compiles even if you don’t import everything manually.

Using Directives

using tells the compiler which namespaces to search for type names so you don’t have to fully qualify them.

Standard using

using System;
using System.Collections.Generic;

Static using (import static members)

using static System.Console;

class Demo
{
    public void Print() => WriteLine("No need for Console.");
}

Alias using (rename a type/namespace)

using IO = System.IO;

class Logs
{
    public string ReadFirstLine(string path)
    {
        using var reader = new IO.StreamReader(path);
        return reader.ReadLine();
    }
}

Global using (applies to the whole project)

Create GlobalUsings.cs once and forget repetitive imports:

// File: GlobalUsings.cs
global using System;
global using System.Collections.Generic;

Gotcha: Overusing global using can hide dependencies. Keep the list small and generic (e.g., System, System.Linq).

Namespace Declaration

A namespace groups related types and avoids name collisions. There are two styles:

Block-scoped

namespace Company.Project.Module
{
    public class Greeter { }
}

File-scoped (modern, cleaner)

namespace Company.Project.Module;

public class Greeter { }

Guideline: Prefer file‑scoped namespaces for brevity. Use company/product/module patterns for clarity, e.g., Contoso.Inventory.Api.

Class Declaration

A class defines the shape and behavior of objects.

namespace Basics;

public class Person
{
    // Fields (internal state)
    private readonly Guid _id = Guid.NewGuid();

    // Auto-property with public get/set
    public string FirstName { get; set; }

    // Property with a private setter (encapsulation)
    public string LastName { get; private set; }

    // Computed (read-only) property
    public string FullName => $"{FirstName} {LastName}".Trim();

    // Constructor (runs when creating an instance)
    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName  = lastName;
    }

    // Method (behavior)
    public string Introduce() => $"Hi, I’m {FullName}!";
}

Access modifiers: public, internal, protected, private, protected internal, private protected. Start strict (private) and open up only as needed.

Other modifiers: static (no instance), sealed (cannot be inherited), abstract (incomplete; must be derived), partial (split across files), record (for value-like, immutable-by-default models).

Attributes and Behavior (Fields, Properties, Methods)

Think of fields as the raw data, properties as controlled access, and methods as actions.

Fields

  • Store state directly.
  • Usually private.
  • Prefer read-only (readonly) if set once.

Properties

  • Use auto-properties for simple storage: public int Age { get; set; }
  • Add logic in accessors when you need validation or derived values: private int _age; public int Age { get => _age; set => _age = value < 0 ? 0 : value; }
  • init accessor lets you set a property during object creation only: public string Email { get; init; } = "unknown@example.com";

Methods

A method has a signature: name + parameters + return type.

public int Add(int a, int b) => a + b;

public string Format(string name, int? age = null)
    => age is null ? name : $"{name} ({age})";

public void Log(string message, bool important = false)
{
    if (important) Console.Error.WriteLine(message);
    else Console.WriteLine(message);
}

Named/optional args improve readability:

Log(message: "Disk space low", important: true);

Constructors

Constructors initialize objects. You can overload them and chain with this(...).

public class TimerOptions
{
    public int IntervalMs { get; }
    public bool AutoStart { get; }

    public TimerOptions(int intervalMs, bool autoStart)
    {
        IntervalMs = intervalMs;
        AutoStart  = autoStart;
    }

    // Convenience overload (defaults)
    public TimerOptions(int intervalMs) : this(intervalMs, autoStart: true) { }
}

Best practices

  • Validate arguments early; throw informative exceptions.
  • Keep constructors thin; defer heavy work to methods (e.g., Start()).

Main Method – The Program’s Entry Point

The CLR starts your app by calling Main.

Valid signatures

static void Main()
static int Main()
static Task Main()
static Task<int> Main()
static void Main(string[] args)

Async Main is great for I/O-bound startups:

using System.Net.Http;

internal static class Program
{
    private static async Task Main()
    {
        using var http = new HttpClient();
        var ping = await http.GetStringAsync("https://example.com/ping");
        Console.WriteLine($"Service says: {ping}");
    }
}

Top‑level statements compile down to a generated Main for you – identical idea, less ceremony.

Bringing It All Together

Let’s model a tiny domain and wire it to a console app.

// File: Models/TaskItem.cs
namespace Tasks.Model;

public class TaskItem
{
    public Guid Id { get; } = Guid.NewGuid();
    public string Title { get; }
    public bool IsDone { get; private set; }

    public TaskItem(string title)
    {
        Title = string.IsNullOrWhiteSpace(title)
            ? throw new ArgumentException("Title is required", nameof(title))
            : title.Trim();
    }

    public void Complete() => IsDone = true;
}
// File: Services/TaskService.cs
using Tasks.Model;

namespace Tasks.Services;

public class TaskService
{
    private readonly List<TaskItem> _items = new();

    public TaskItem Add(string title)
    {
        var item = new TaskItem(title);
        _items.Add(item);
        return item;
    }

    public IEnumerable<TaskItem> All() => _items;
}
// File: Program.cs (top‑level)
using Tasks.Services;

var service = new TaskService();
service.Add("Learn basic C# syntax");
service.Add("Write first program");

foreach (var item in service.All())
{
    Console.WriteLine($"- [{(item.IsDone ? 'x' : ' ')}] {item.Title}");
}

Project layout (ASCII):

.
├─ Models
│  └─ TaskItem.cs
├─ Services
│  └─ TaskService.cs
└─ Program.cs

This tiny app showcases using directives, namespaces, classes, fields/properties/methods, constructors, and the entry point.

Writing a Simple C# Program (Step‑by‑Step)

  1. Create a folder and a console project: mkdir BasicsApp && cd BasicsApp dotnet new console -n BasicsApp cd BasicsApp
  2. Run it: dotnet run
  3. Replace Program.cs with the Task list sample above.
  4. Add folders/files Models/TaskItem.cs and Services/TaskService.cs.
  5. Run again and observe the output.

Tip: Turn on nullable reference types for safer code by adding <Nullable>enable</Nullable> to your .csproj.

Understanding Namespaces, Classes, and Methods

  • Namespace: a logical group for related types (prevents naming collisions). Think: folders.
  • Class: a blueprint for objects that hold state and behavior. Think: a house plan.
  • Method: an action or question you can ask an object. Think: a doorbell you ring to do something.

If you fully qualify types, you can skip using:

var now = System.DateTime.UtcNow;
System.Console.WriteLine(now);

…but life is short – use using sensibly.

Basic C# Syntax of Namespaces

// File-scoped (preferred)
namespace Company.Product.Feature;

public class Engine { }

// Block-scoped (legacy style)
namespace Company.Product.Feature
{
    public class Engine { }
}

Naming Tips

  • Use PascalCase for namespaces and classes: Contoso.Payments.Api.
  • Keep them stable; changing a namespace is a breaking change for library consumers.

Basic C# Syntax of Classes

public class Car
{
    // Field
    private int _odometer;

    // Auto-property
    public string Model { get; init; }

    // Property with logic
    public int Odometer
    {
        get => _odometer;
        private set => _odometer = value < 0 ? 0 : value;
    }

    // Constructor
    public Car(string model)
    {
        Model = model ?? throw new ArgumentNullException(nameof(model));
        Odometer = 0;
    }

    // Method
    public void Drive(int kilometers)
    {
        if (kilometers <= 0) return;
        Odometer += kilometers;
    }
}

Rule of thumb: Start with auto-properties; move to backing fields only when you need logic or performance tweaks.

Basic C# Syntax of Methods

public class MathOps
{
    // 1) Simple method
    public int Add(int a, int b) => a + b;

    // 2) Overload with different params
    public double Add(double a, double b) => a + b;

    // 3) Optional + named parameters
    public string Pad(string text, int totalWidth = 10, char padChar = ' ')
        => text.PadLeft(totalWidth, padChar);

    // 4) Out parameters (multiple results)
    public bool TryParseInt(string input, out int number)
        => int.TryParse(input, out number);

    // 5) Async method
    public async Task<string> DownloadAsync(HttpClient http, string uri)
        => await http.GetStringAsync(uri);
}

Don’t overuse out – prefer clear return types or small structs/records to carry multiple values.

Common Pitfalls (and quick fixes)

  • Forgetting access modifiers: class defaults to internal in top-level scope. Be explicit (public) for libraries.
  • Using fields instead of properties in public APIs: prefer properties for binary compatibility and binding.
  • Business logic in constructors: move I/O or heavy work to methods (e.g., InitializeAsync).
  • Ignoring nullability: enable <Nullable>enable</Nullable> and act on warnings.
  • God classes: if a class exceeds ~200–300 lines and does many things, split responsibilities.

Mini Checklist

  • Namespace is file-scoped and meaningful.
  • Public types/members use PascalCase; locals/params use camelCase.
  • Constructors validate inputs.
  • Methods are short, do one thing, and have clear names.
  • Properties encapsulate state; fields stay private.
  • Main (or top‑level) is minimal – delegate to services.

FAQ: Your First C# Program

Top‑level statements or classic Main?

For small apps and samples, top‑level is faster to read. For bigger apps, an explicit Main keeps startup organized.

What’s the difference between a field and a property?

A field is raw storage; a property is a method-backed accessor (even if auto-generated) that can add logic, validation, and is friendlier to tooling and binding.

Can Main be async?

Yes – use static async Task Main() (or Task<int>). Await I/O cleanly without blocking.

Do I need a namespace in tiny scripts?

No, but add one for anything you plan to reuse. Namespaces keep code organized and conflict-free.

Should I use records for models?

If the type is mostly data and equality/value semantics matter, record (or record class) is great. For behavior-rich entities, stick with classes.

Where do using directives go?

At the top of the file, before the namespace. Keep them sorted and minimal. Use global using for widely shared imports.

Conclusion: From syntax to a working app

You just mapped the city of C#: using → namespace → class → fields/properties → methods → constructors → Main. With these parts clear, you can navigate any codebase and ship working programs with confidence. Now it’s your turn – fork the sample, add a command to complete a task, and post your output in the comments. What’s the first feature you’ll build?

Leave a Reply

Your email address will not be published. Required fields are marked *