80. implicit operator
An implicit operator in C# defines automatic type conversions between user-defined types and other types without requiring an explicit cast. It prevents unnecessary casting syntax while ensuring type safety.
To define an implicit conversion, use the implicit keyword inside a class:
public class Temperature
{
public double Celsius { get; }
public Temperature(double celsius) { Celsius = celsius; }
public static implicit operator double(Temperature temp) => temp.Celsius;
public static implicit operator Temperature(double value) => new Temperature(value);
}
Usage:
Temperature temp = 37.5; // Automatically converts from double to Temperature
double degrees = temp; // Automatically converts from Temperature to double
Unlike explicit conversions (explicit operator), implicit operators avoid casting in the user’s code, making conversions seamless while preventing data loss.
Implicit operators are useful when:
- Converting between compatible types (int ↔ Fraction, double ↔ Currency).
- Simplifying object creation from primitive types.
- Avoiding redundant .ToX() or casting ((T)x) in code.
However, implicit conversions should be used with caution to avoid unintended conversions, especially when data loss is possible. When conversions are not always safe, explicit operators are preferred.
81. explicit operator
An explicit operator in C# defines a custom type conversion that requires an explicit cast ((T)) in user code. Unlike an implicit operator, which allows automatic conversion, an explicit operator ensures that the developer consciously acknowledges the conversion, typically when there’s a risk of data loss.
To define an explicit conversion, use the explicit keyword inside a class:
public class Temperature
{
public double Celsius { get; }
public Temperature(double celsius) { Celsius = celsius; }
public static explicit operator int(Temperature temp) => (int)temp.Celsius;
}
public class Temperature
{
public double Celsius { get; }
public Temperature(double celsius) { Celsius = celsius; }
public static explicit operator int(Temperature temp) => (int)temp.Celsius;
}
Usage:
Temperature temp = new Temperature(36.7);
int roundedTemp = (int)temp; // Explicit cast required
This prevents unintended truncation of decimal values.
Explicit operators are ideal when conversions are not always safe—for example, converting a double to an int may result in loss of precision. They provide a safeguard by forcing the developer to use an explicit cast, ensuring intentional conversion.
While explicit conversions enhance clarity, excessive reliance on them can lead to unreadable code. Use them when necessary, but favor safer alternatives when possible.
82. conversion operator
A conversion operator in C# defines how a custom type can be converted to or from another type. There are two types:
- Implicit conversion (implicit operator) – Automatic, safe conversion (no data loss).
- Explicit conversion (explicit operator) – Requires an explicit cast to prevent unintended data loss.
Example:
public class Currency
{
public double Amount { get; }
public Currency(double amount) { Amount = amount; }
// Implicit conversion from double to Currency
public static implicit operator Currency(double value) => new Currency(value);
// Explicit conversion from Currency to int
public static explicit operator int(Currency c) => (int)c.Amount;
}
Usage:
Currency money = 50.75; // Implicit conversion
int roundedMoney = (int)money; // Explicit conversion required
Conversion operators improve code readability by reducing redundant .ToX() methods. They are commonly used in mathematical, unit-based, or custom data structures where seamless type interoperability is needed.
However, avoid overloading too many conversions, as it can make the code harder to understand. Always prioritize clarity and safety when defining conversion rules.
83. indexer
An indexer in C# allows objects to be accessed using array-like syntax ([]). It is useful when designing classes that act like collections but are not necessarily arrays or lists.
Indexers are defined using the this keyword:
public class Week
{
private string[] days = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
public string this[int index]
{
get => days[index];
set => days[index] = value;
}
}
Usage:
Week week = new Week();
Console.WriteLine(week[0]); // Output: Sunday
week[0] = "Funday";
Console.WriteLine(week[0]); // Output: Funday
Key Features:
✔ Provides array-like access to
objects.
✔ Supports read-only (get) or
modifiable (get and set) properties.
✔ Can be overloaded with
multiple parameters (this[int, int]).
✔ Commonly used in custom
collections, matrices, or structured data storage.
Indexers improve code readability by making data retrieval intuitive while keeping the internal structure encapsulated. They allow developers to customize how elements are accessed while enforcing logic inside the get/set blocks.
84. pointer types
Pointer types in C# allow direct memory manipulation but are only permitted in unsafe code blocks. Unlike references (object, class), pointers store memory addresses directly, similar to C/C++.
Pointer syntax:
unsafe
{
int number = 10;
int* ptr = &number; // Pointer stores the address of 'number'
Console.WriteLine(*ptr); // Dereferencing pointer to get value
}
Key Rules for Pointers:
✔ Declared using * (e.g., int* p).
✔ Can store the memory address of a
variable (&var).
✔ Can be dereferenced using *ptr to
access the value.
✔ Used with structs, arrays, and
interop with unmanaged code.
✔ Only allowed in unsafe blocks and
require /unsafe compiler option.
Common pointer types:
- int*, double*, char* → Stores the address of primitive types.
- void* → Generic pointer without a specific type.
- T* → Used for unmanaged struct types.
Pointer arithmetic is also allowed:
unsafe
{
int arr[] = {1, 2, 3};
fixed (int* ptr = arr)
{
Console.WriteLine(*(ptr + 1)); // Access second element
}
}
While powerful, pointers bypass .NET’s memory safety model and can lead to issues like memory leaks or buffer overflows. They are primarily used in performance-critical applications, system programming, or interop with unmanaged code (e.g., working with C libraries).
For most applications, safe alternatives like Span<T>, Memory<T>, or IntPtr should be preferred unless raw memory access is necessary.
85. unsafe code
Unsafe code in C# allows direct memory manipulation using pointers, bypassing the .NET runtime's safety features like garbage collection and type checking. It is primarily used for interacting with unmanaged code, improving performance, or working with low-level system resources.
To enable unsafe code:
- Use the unsafe keyword in a block, method, or class.
- Enable the /unsafe compiler option.
Example:
unsafe
{
int number = 42;
int* ptr = &number; // Pointer stores memory address of 'number'
Console.WriteLine(*ptr); // Dereference pointer (prints 42)
}
✔ Common Use Cases:
- Interacting with native libraries (DLLImport, PInvoke).
- Performance optimization (avoiding bounds checking in arrays).
- Memory management (allocating/freeing memory manually).
✔ Fixed Pointers with Structs:
unsafe
{
fixed (char* ptr = "Hello")
{
Console.WriteLine(ptr[0]); // Prints 'H'
}
}
✔ Unsafe Code in Structs:
unsafe struct MyStruct
{
public fixed int Numbers[5]; // Fixed-size array inside a struct
}
🚨 Risks of Unsafe
Code:
⚠ Bypasses type safety → Risk of
buffer overflows and memory corruption.
⚠ Manual memory management → Can
cause memory leaks.
⚠ Harder to debug → No automatic
garbage collection for unsafe operations.
🔹 Alternatives:
In most cases, .NET provides safer alternatives like:
✅ Span<T> and Memory<T> (for efficient, safe
memory access).
✅ IntPtr (for safe pointer-like functionality).
✅ Marshal class (for managed-unmanaged interop).
Use unsafe code only when absolutely necessary, ensuring it is well-tested and optimized for performance-critical tasks like game engines, device drivers, or high-performance computing.