C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
64: Conversion Operators: Implicit and Explicit
A lot of developers I've mentored assume that casting—the act of telling C# "treat this thing as that type"—is a built-in magic trick reserved for primitive types like int and double, or for moving up and down an inheritance hierarchy. They think that if they've built a custom class or struct, they are stuck writing "converter" methods like ToDecimal() or FromDouble() for the rest of eternity.
Casting is only for built-in types and inheritance
Let's look at why that's a limiting way to think. Imagine you have a Money struct. You want to be able to use it in a calculation with a standard decimal without calling a method every single time. If you try this without conversion operators, it fails:
public struct Money
{
public decimal Amount { get; set; }
}
Money wallet = new Money { Amount = 10.50m };
decimal total = wallet; // CS0029: Cannot implicitly convert type 'Money' to 'decimal'
The compiler doesn't care that Money only contains a decimal. To the C# compiler, these are two completely different types. You might be tempted to just add a GetAmount() method, but that clutters your business logic with "plumbing" calls. It makes your code feel like you're fighting the language rather than using it.
Teaching C# how to treat your objects as other types
This is where implicit and explicit operators come in. You can actually define the rules for how your custom type transforms into another type. I like to think of it as defining a "bridge" between two types.
An implicit conversion is a bridge that the compiler is allowed to cross automatically. You use this when the conversion is "safe"—meaning there is no risk of data loss or an exception. An explicit conversion is a bridge that requires a manual "push" (a cast), which signals to other developers that something potentially risky is happening.
Let's fix our Money struct. We'll make it implicit to convert Money to decimal (since that's always safe), but explicit to convert a decimal back into Money (because we want to be intentional about creating a value object):
public struct Money
{
public decimal Amount { get; }
public Money(decimal amount) => Amount = amount;
// Implicit: No cast needed. Safe and seamless.
public static implicit operator decimal(Money money) => money.Amount;
// Explicit: Requires (Money)cast. Intentional.
public static explicit operator Money(decimal amount) => new Money(amount);
}
// Now this works automatically:
Money wallet = new Money(10.50m);
decimal total = wallet;
// This requires a cast:
decimal rawValue = 20.00m;
Money payment = (Money)rawValue;
Notice the syntax: public static [implicit/explicit] operator [TargetType]([SourceType] value). It's a bit wordy, but it's powerful. I generally suggest using implicit only when the conversion is truly lossless. If you're converting a double to an int, or a User object to a string, go explicit. You want the person reading your code to see that cast and think, "Okay, something is being transformed here," rather than wondering why a type suddenly changed mid-sentence.
- Implicit operators make your API feel cleaner and more natural.
- Explicit operators protect you from accidental data loss or unexpected type changes.
- Both must be
public static.
📋 Practical Task
Implementing Weight-to-Double Conversion Logic
You are building a fitness tracking application. You have a Weight struct that stores a value in kilograms. To ensure the rest of the app can perform calculations easily, you need to implement conversion operators.
Your Task:
- Create a
struct Weightwith a privatedouble _kilogramsfield and a constructor to set it. - Implement an implicit operator that allows a
Weightobject to be treated as adouble. - Implement an explicit operator that allows a
doubleto be converted into aWeightobject. - In your
Mainmethod, demonstrate both: assign aWeightobject to adoublevariable without a cast, and assign adoubleto aWeightvariable using a cast.
There are no comments for now.