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.