Narrowing and Widening Data Type Conversions

Formally speaking, widening is the term used to define an implicit upward cast that does not result in a loss of data. Narrowing is the logical opposite of widening in that a larger value stored within a smaller data type variable.

When you want to inform the compiler that you are willing to deal with a possible loss of data because of a narrowing operation, you must apply an "explicit cast" using the C# casting operator, ().

short numb1 = 30000, numb2 = 3000;
// Explicity cast the int into a short (and allow loss of data) 
short answer = (short)Add(numb1, numb2);
static int Add(int num1, int num2)
 {
     return num1 + num2;
 } 

An explicit cast allows you to force the compiler to apply a narrowing conversion, even when doing so may result in a loss of data.

If you are building an application where loss of data is unacceptable, C# provides the "checked" and "unchecked" keywords to ensure data loss does not escape undetected.

Using the checked keyword

To handle overflow or underflow conditions in your application, you have 2 options.

  1. Your first choice is to leverage your with and programming skills to handle all overflow/underflow conditions manually.
  2. C# provides the "checked" keyword. When you wrap a statement (or a block of statements) within the scope of checked keyword, the C# compiler emits additional CIL instructions that test for overflow conditions that may result when adding, multiplying, subtracting, or dividing 2 numerical data types. If an overflow has occurred, you will receive a runtime exception. System.OverflowException.

static void Main(string[] args)
 {
     try
     {
         byte b1 = 4;
         byte b2 = 254;
         byte sum = (byte)Add(b1, b2);
         Console.WriteLine($"sum = {sum}");
     }
     catch (Exception e)
     {
         Console.WriteLine(e.Message);
     }
 }
private static byte Add(byte b1, byte b2)
 {
     return checked((byte)(b1 + b2));
 }

The code will be evaluated for possible overflow conditions automatically, which will trigger an overflow exception if encountered.

--- Setting Project-Wide Overflow Checking ---

When it's enable all your arithmetic will be evaluated for overflow without the need to make use of the C# checked keyword! Add project file;

<PropertyGroup>
        <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
</PropertyGroup>

Using the unchecked Keywords

C# provides unchecked keyword to disable the throwing of an overflow exception on a case-by-case basis.

private static byte Add(byte b1, byte b2)
 {
     return unchecked( (byte)(b1 + b2));
 }

Default behavios is already unchecked.

Remember that the default behavior of the .NET Core runtime is to ignore arithmetic overflow/underflow. When you want to selectively handle discrete statements, use the "checked" keyword. If you want to trap overflow errors throughout your application, enable the /checked flag. Finally, the unchecked keyword can be used if you have a block of code where overflow is acceptable (thus shouldn't trigger a runtime exception).

Understanding Implicitly Typed Local Variables

Explicitly typed local variables are declared as follows;

// dataType variableName = initialValue; 
string myString = "Time, marches on..."; 

The C# language does provide for "implicitly typing" local variables using the "var" keyword. This keyword can be used in place of specifying a specific data type (such as int, bool, or string). When you do so, the compiler will automatically infer the underlying data type based on the initial value used to initialize the local data point.

var myString = "Time, marches on..."; 

Reflection is the act of determining the composition of a type at runtime.

Console.WriteLine(myString.GetType().Name);


Declaring Numerics Implicitly

Whole numbers default to integers, and floating-point numbers default to doubles.

var myUInt = 0u; 
var myInt = 0; 
var myLong = 0L; 
var myDouble = 0.5; 
var myFloat = 0.5F; 
var myDecimal = 0.5M; 


Understanding Restrictions on Implicitly Typed Variables

Rule 1: It's illegal to use the var keyword to define return values, parameters, or field data of a custom type.

class ThisWillNeverCompile { 
    // Error! var can not be used as field data. 
    private var myInt = 10; 
    // Error! var cannot be used as a return value or parameter type. 
    public var MyMethod(var x,var y){} 
} 


Rule 2: Also, local variables declared with the var keyword must be assigned an initial value at the exact time of declaration and cannot be assigned the initial value of null. This last restriction should make sense, given that the compiler cannot infer what sort of type in memory the variable would be pointing to based only on null.

// Error! Must assign a value.
var myData;
 
// Error! Must assign a value at exact time of declaration.
var myData;
myData = 0;
 
// Error! Can't assign null as initial value.
var myObj = null;


Rule 3: It is permissible, however, to assign an inferred local variable to null after its initial assignment.

// OK! if SportsCar is a refence Type.
var myCar = new SportsCar();
myCar = null;

Rule 4: Furthermore, it is permissable to assign the value of an implicitly types local variable to the value of other variables, implicitly typed or not.

// Also, OK!
var myInt = 0;
var anotherInt = myInt;
 
string myString = "Wake Up!";
var myData = myString;

Rule 5: Also, it is permissible to return an implicitly typed local variable to the caller, provided the method return type is the same underlying type as the var-defined data point.

static int GetAnInt(){
    var retVal = 9;
    return retVal;
}