C# casting and type conversion — explicit cast, as, is, Convert, implicit

Type conversion in C# comes in several forms: implicit conversions (automatic, always safe), explicit casts (manual, may throw), the as/is operators (safe patterns), and helper methods like Convert.ToInt32(). Choosing the right one avoids both silent data loss and unexpected exceptions.

Implicit Conversion

The compiler inserts implicit conversions automatically when there is no risk of data loss. No cast syntax is needed.

C# Example Code
int    i = 42;
long   l = i;     // int → long  (widening, always safe)
float  f = i;     // int → float (safe, minor precision change)
double d = i;     // int → double

Console.WriteLine(l); // 42
Console.WriteLine(d); // 42

// Also works for derived → base class
object obj = "hello"; // string → object (always valid)

Explicit Cast

Use explicit cast (Type)value when converting a wider type to a narrower one. If the value doesn't fit, a OverflowException or data loss occurs.

C# Example Code
double d = 9.99;
int    i = (int)d;    // truncates — no rounding
Console.WriteLine(i); // 9

long big = 3_000_000_000L;
int  small = (int)big; // overflows silently (unchecked context)
Console.WriteLine(small); // -1294967296 (garbage!)

// Use checked to throw instead of silently overflow:
try
{
    int safe = checked((int)big);
}
catch (OverflowException ex)
{
    Console.WriteLine($"Overflow: {ex.Message}");
}

as Operator — Safe Reference Cast

as attempts the cast and returns null if it fails, instead of throwing. Only works for reference types and nullable value types.

C# Example Code
object obj = "Hello, world!";

string? s = obj as string;
if (s != null)
    Console.WriteLine(s.Length); // 13

// Returns null for incompatible types — no exception
object num = 42;
string? fail = num as string;
Console.WriteLine(fail); // (null)

is Operator — Pattern Check

is tests the type without throwing. Combine it with a pattern variable to cast and bind in one step.

C# Example Code
object[] items = { "text", 42, 3.14, true, "another" };

foreach (var item in items)
{
    if (item is string s)
        Console.WriteLine($"String of length {s.Length}: {s}");
    else if (item is int n)
        Console.WriteLine($"Integer: {n}");
    else
        Console.WriteLine($"Other: {item}");
}

// Output:
// String of length 4: text
// Integer: 42
// Other: 3.14
// Other: True
// String of length 7: another

as vs Direct Cast

(T)valuevalue as T
On failureThrows InvalidCastExceptionReturns null
Works with value typesYesNo (only reference/nullable)
PerformanceSlightly fasterOne null check
Use whenYou're certain of the typeType might not match
C# Example Code
object obj = "hello";

// Direct cast — throws if wrong type
string direct = (string)obj;

// as cast — returns null if wrong type
string? safe = obj as string;

Convert Class

System.Convert provides explicit conversions between base types and handles null by returning the default value (unlike a direct cast which throws).

C# Example Code
string s = "42";
int    i = Convert.ToInt32(s);      // "42" → 42
double d = Convert.ToDouble("3.14"); // "3.14" → 3.14
bool   b = Convert.ToBoolean("true"); // "true" → true

// Convert.ToInt32(null) returns 0; (int)null would throw
object? nullObj = null;
int zero = Convert.ToInt32(nullObj);
Console.WriteLine(zero); // 0

Convert.ToInt32() rounds — Convert.ToInt32(3.7)4. The explicit cast (int)3.7 truncates → 3.

Parse vs Convert vs Cast

C# Example Code
string s = "100";

// Parse — string → value type, throws on failure
int a = int.Parse(s);

// TryParse — safe, returns bool
int b;
bool ok = int.TryParse(s, out b);

// Convert — handles null, uses rounding for floats
int c = Convert.ToInt32(s);

// Cast — only between numeric types, not from string
double d = 100.9;
int e = (int)d; // truncates to 100

User-Defined Conversions

Classes can define implicit and explicit conversion operators.

C# Example Code
public class Celsius
{
    public double Value { get; }
    public Celsius(double value) => Value = value;

    // Explicit: caller must write (Fahrenheit)celsius
    public static explicit operator Fahrenheit(Celsius c)
        => new Fahrenheit(c.Value * 9 / 5 + 32);

    public override string ToString() => $"{Value}°C";
}

public class Fahrenheit
{
    public double Value { get; }
    public Fahrenheit(double value) => Value = value;

    // Implicit: assignment is automatic
    public static implicit operator Celsius(Fahrenheit f)
        => new Celsius((f.Value - 32) * 5 / 9);

    public override string ToString() => $"{Value}°F";
}

var boiling = new Celsius(100);
var fahr    = (Fahrenheit)boiling;   // explicit required
Console.WriteLine(fahr);             // 212°F

Fahrenheit body = new Fahrenheit(98.6);
Celsius    c    = body;              // implicit — no cast needed
Console.WriteLine(c);               // 37°C

Casting in Generics

Use as or is when working with unconstrained generic types.

C# Example Code
public static T? TryCast<T>(object obj) where T : class
    => obj as T;

string? s = TryCast<string>("hello");  // "hello"
string? n = TryCast<string>(42);       // null

Console.WriteLine(s); // hello
Console.WriteLine(n); // (null)

Quick Reference

MethodSafe?Null handlingRounds or truncates
(T)valueNo — throws InvalidCastExceptionThrowsTruncates
as TYes — returns nullReturns nullN/A (reference)
is T xYes — returns boolReturns falseN/A
Convert.ToX()Throws on bad inputReturns defaultRounds
T.Parse()Throws on bad inputThrowsN/A
T.TryParse()Yes — returns boolReturns falseN/A