CS File Documentation


Summary

A C# Source Code File is a plain-text file holding program code written in Microsoft’s C# language for the .NET platform. Any text editor opens a .cs file (MIME text/plain), so the real split is reading versus running: to compile and run it you need a C# toolchain such as the free .NET SDK, VS Code with the C# Dev Kit, or Visual Studio. On its own a .cs file does nothing until it is compiled into a .dll or .exe.

Technical details

FeatureValue
Full nameC# (Visual C#) Source Code File
File extension.cs
MIME typetext/plain (also text/x-csharp)
Format typePlain-text programming-language source code
DeveloperMicrosoft (C# language)
Introduced2002 (C# 1.0 with .NET Framework 1.0 and Visual Studio .NET)
Latest language versionC# 13 (.NET 9, November 2024)
StandardECMA-334; ISO/IEC 23270
Open standardYes — the C# language is standardised
EncodingUTF-8 (optionally with BOM); legacy files may be ANSI or UTF-16
Byte orderN/A for UTF-8; little-endian if saved as UTF-16
Container / basePlain text (no container)
CompressionNone
Magic numberNone — no binary signature; identify by .cs extension and C# syntax
Compiled or interpretedCompiled — the Roslyn/csc compiler emits IL in a .dll or .exe
Typical size1–200 KB (one type per file by convention)
Can containusing directives, namespaces, classes, structs, records, interfaces, enums, methods
Comment syntax// line, /* */ block, /// XML doc
Associated toolsVisual Studio, VS Code + C# Dev Kit, JetBrains Rider, .NET SDK
PlatformWindows, macOS, Linux (any system with the .NET SDK or Mono)
Related extensions.csproj, .sln, .csx, .cshtml, .dll, .exe, .vb, .fs
Specificationlearn.microsoft.com/dotnet/csharp/
Syntax at a glance

A .cs file is plain UTF-8 text with no file signature; an optional UTF-8 BOM (EF BB BF) may precede the code. Most files open with using directives that import namespaces, followed by a namespace declaration (block or file-scoped) and one or more type declarations: class, struct, record, interface, or enum. Statements end with a semicolon and code blocks sit inside curly braces { }. Comments use // for a line, /* */ for a block, and /// for XML documentation. The compiler (Roslyn/csc, run by the .NET SDK or an IDE) reads the source and emits intermediate language into a .dll or .exe assembly — the .cs itself is never executed directly.

What is a CS file?

A CS file holds source code written in C# (pronounced “C-sharp”), the object-oriented language Microsoft designed for the .NET platform. The .cs extension marks a plain-text document: a stream of Unicode characters, normally UTF-8, that a person or a tool can read directly. It carries no binary header and no compiled code. Everything in a .cs file is human-readable program text that the C# compiler will later parse and translate.

C# was announced in 2000 and shipped in January 2002 with the .NET Framework 1.0 and Visual Studio .NET as C# 1.0. The language is an open standard, published by Ecma International as ECMA-334 and by ISO/IEC as ISO/IEC 23270; the specification defines the grammar, the type system, and the lexical rules that every .cs file must follow. The language has moved through many revisions, reaching C# 13 with .NET 9 in November 2024. Files with this extension appear across the whole .NET world: ASP.NET web back-ends, WPF and WinForms desktop apps, MAUI mobile projects, cloud services, and Unity game scripts are all authored in .cs files.

The important point for anyone reading the raw bytes is that a .cs file is passive. It is never executed as-is. The C# compiler reads it, checks it against the language rules, and emits a separate binary DLL or EXE assembly. The rest of this page follows that path in detail: the encoding of the text, the structure of a compilation unit, the compiler pipeline that turns C# into a running program, and how loose .cs files are gathered into a single build.

The .cs file as a UTF-8 text stream

Physically a .cs file is a sequence of bytes with no fixed signature. It has no magic number, no length field, and no checksum inside the file. The compiler and every editor identify it by the .cs extension and by its contents (a using directive, a namespace, or a // comment at the top are typical). The recommended encoding is UTF-8. Files may begin with an optional UTF-8 byte-order mark, the three bytes EF BB BF, though it is not required and modern tooling reads the file correctly with or without it. Legacy files may still be saved as UTF-16 (little-endian on Windows) or an ANSI code page, and the Roslyn compiler will detect a BOM to choose the encoding; without a BOM it defaults to UTF-8.

The C# lexical specification treats the input as a stream of Unicode scalar values. Line terminators may be carriage return, line feed, or the pair; identifiers may contain Unicode letters, and string literals may hold any Unicode text. Because the file is plain text, it carries no integrity data of its own. When you build with debug symbols the compiler can record a hash of the source inside the program database (PDB) so a debugger can confirm the .cs has not changed, but the .cs file itself stores no such hash.

The compilation unit

Each .cs file is one compilation unit in the language grammar. A compilation unit has a fixed top-to-bottom order: extern alias directives (rare), then using directives, then global attributes, then a sequence of namespace and type declarations. Getting that order wrong is a compile error, not a style issue.

using directives import a namespace so its types can be named without a fully qualified path: after using System; you can write Console instead of System.Console. A using can also create an alias (using Json = System.Text.Json;) or import static members (using static System.Math;). Since C# 10, a directive marked global using applies to every file in the project, which is how SDK-style projects supply common namespaces implicitly.

A namespace groups types and prevents name collisions. It can be written as a block (namespace Acme.Billing { ... }) or, since C# 10, as a file-scoped declaration (namespace Acme.Billing;) that applies to the whole file with less indentation. Inside a namespace sit the type declarations: class, struct, record, interface, enum, and delegate. Each type contains members: fields, properties, constructors, methods, events, and nested types. By convention one .cs file declares one main public type, though the language does not require it.

Top-level statements versus an explicit Main

A program needs one entry point. Traditionally that is a method with the exact signature static void Main(string[] args) (variants returning int or Task are allowed) inside a class. Here is a compilation unit written the classic way:

using System;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, world!");
        }
    }
}

Since C# 9 and .NET 5, a single file in the project may use top-level statements: executable statements written directly at the top of the file, with no enclosing class or Main. The compiler generates the hidden entry-point method and wraps the statements in it. The whole program above collapses to:

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

Only one file per compilation may hold top-level statements, and they must precede any type declarations in that file. The implicit args parameter and an implicit return value are still available. Both forms produce the same kind of entry point in the compiled assembly; top-level statements are purely a source-level convenience.

From C# to CIL to a PE assembly

Turning a .cs file into something that runs is a multi-stage pipeline, and none of the stages executes the .cs directly. The compiler is Roslyn (the executable is csc), the open-source .NET compiler platform normally invoked by the .NET SDK or an IDE rather than run by hand.

First, Roslyn lexes and parses the source into a syntax tree: an in-memory tree whose nodes mirror the grammar (a namespace node containing a class node containing method nodes, and so on). Trivia such as whitespace and comments are attached to the tree but do not affect the code. Next comes binding: the compiler resolves every identifier to a symbol, checks types, applies overload resolution, and reports errors. Finally the emit phase writes out Common Intermediate Language (CIL, also called IL or MSIL), a stack-based, CPU-independent instruction set, together with the metadata tables that describe every type, method, field, and reference.

The IL and metadata are packaged into a PE (Portable Executable) assembly: the same container Windows uses for native binaries, but carrying a CLI header that marks it as managed. A class-library build produces a .dll; an application build produces an .exe (on .NET Core / .NET 5+ the .exe is a small native launcher plus a managed .dll of the same name). Either way the file holds portable CIL, not native machine code.

At run time the Common Language Runtime (CLR) loads the assembly, reads its metadata, and hands each method to the JIT compiler, which translates that method’s CIL into native instructions for the current CPU the first time it is called. (Ahead-of-time options such as ReadyToRun and Native AOT can move some or all of that translation to build time.) This is the concrete reason a .cs never runs on its own: three artefacts exist, and they are different things — the .cs source text, the CIL-plus-metadata inside the PE assembly, and the native code the JIT produces in memory. The compiler reads the first and writes the second; the runtime reads the second and produces the third.

How .cs files map to a .csproj and an assembly

A single .cs file is rarely a build on its own. It belongs to a project defined by a CSPROJ file, an XML document that the SDK reads to decide what to compile and how. In the SDK-style format, every .cs file under the project folder is included automatically, so the project file stays short. A minimal console .csproj looks like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
  </ItemGroup>

</Project>

The <TargetFramework> element holds a target framework moniker (TFM) such as net9.0, net8.0, or net48 for the older Windows-only .NET Framework 4.8. The TFM chooses which runtime and which base class library the code compiles against, and it also sets the default C# language version (net9.0 defaults to C# 13). OutputType decides whether the build emits an EXE or a DLL. PackageReference pulls in a NuGet dependency, and ProjectReference (not shown) links to another project’s assembly.

When you run dotnet build, MSBuild expands the project into the full set of .cs files, adds the framework and package references, and passes them all to Roslyn as one compilation. All of those files are compiled together into a single assembly: a class defined in Order.cs can use one defined in Customer.cs with no import beyond a matching namespace, because they share one compilation and one output assembly. This is why one loose .cs often will not build alone: it references types that live in sibling files or in referenced packages, and the target framework it needs is declared in the .csproj, not in the source. Several projects are grouped by a .sln solution file for tooling, but each project still compiles to its own assembly.

Language features that shape the source text

Several C# features change what a .cs file looks like, even though they all reduce to the same CIL. Understanding them helps when reading unfamiliar source.

Partial types let one class, struct, or interface be split across several .cs files with the partial keyword; the compiler merges the parts into one type. Tooling relies on this heavily, keeping generated code (a designer file, a source-generator output) in a separate file from the hand-written half. Records (record and record struct, from C# 9/10) are types whose compiler synthesises value equality, a constructor, and a ToString from a short positional declaration, so a data type can be a single line. Async/await methods marked async with await expressions are rewritten by the compiler into a state-machine struct that implements the awaited continuations; the source reads like straight-line code, but the emitted IL is a generated state machine. LINQ query syntax (from x in xs where ... select ...) is translated by the compiler into ordinary method calls (Where, Select) over IEnumerable<T> or IQueryable<T>. And the using/namespace machinery described earlier is what keeps all of this addressable across files. None of these appear as distinct constructs in the assembly; they are source-level shapes the compiler lowers to plain IL.

Why a .cs never runs on its own

Double-clicking a .cs file runs nothing, because the operating system has no loader for C# source. Execution needs the two-step translation covered above: Roslyn compiles the source to a PE assembly, then the CLR JIT-compiles that assembly’s CIL to native code. The practical commands make the boundary clear. dotnet build compiles the project’s .cs files into an assembly on disk; dotnet run builds and then launches it; dotnet publish -c Release produces a distributable build, optionally self-contained with the runtime bundled. Modern .NET also offers file-based apps, where dotnet run app.cs compiles and runs a single file, but it is still compiled behind the scenes into an assembly before anything executes.

C# is not the only .NET language that compiles to the same CIL: VB (Visual Basic .NET) and F# emit compatible assemblies, which is why a C# project can reference a library written in another .NET language. And C# source is not always confined to a .cs file. In ASP.NET Core, CSHTML Razor files interleave C# with HTML; the Razor tooling generates ordinary C# that the same Roslyn pipeline then compiles. In every case the rule holds: the .cs (or the C# generated from a .cshtml) is source, the assembly is the compiled result, and the two are never interchangeable.

References