In this article, we will explore different methods to check if an object is a number.

To download the source code for this article, you can visit our GitHub repository.

C# is a strongly typed programming language, which ensures type safety and improves code stability. Therefore, when working with C#, it’s often necessary to check if an object is of a particular type. 

The Numeric Types in C#

There are two categories of numeric types in C#: integral and floating-point. It is important to note that all numeric data types, both integral and floating point types, are value types.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

So when writing code, we will have to select a specific data type depending on the data we want to store and manipulate.

Let’s take a look at the Integral numeric types:

.Net TypeRangeC# keyword
System.SByte-128 to 127sbyte
System.Byte0 to 255byte
System.Int16-32,768 to 32,767short
System.UInt160 to 65,535ushort
System.Int32-2,147,483,648 to 2,147,483,647int
System.UInt320 to 4,294,967,295uint
System.Int64-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807long
System.UInt640 to 18,446,744,073,709,551,615ulong
System.Int128-170141183460469231731687303715884105728 to

170141183460469231731687303715884105727
System.UInt1280 to 340282366920938463463374607431768211455
System.IntPtrDepends on platform (computed at runtime)nint
System.UIntPtrDepends on platform (computed at runtime)nuint

Now, let’s continue with the Floating-point numeric types:

.Net TypeApproximate rangeC# keyword
System.Half± 65504
System.Single± 3.4 x 10³⁸float
System.Double± 1.7 × 10³⁰⁸double
System.Decimal± 7.9228 x 10²⁸decimal

We must remember that the base class for all data types in C# is System.Object. This means that we can assign any type to a variable of type object.

Let’s see how we can check if an object is numeric.

Check If an Object Is a Number With the Equality Operator

The equality operator is a binary operator, which means it works with two operands. The equal operator compares if the two operands (or expressions) are strictly equal and returns a bool result.

Throughout this article, we will add functions to a static Methods class. Let’s start with a method that determines whether an object is an int using the equality operator:

public static bool CheckIfIntegerWithEqualityOperator(object value)
    => value.GetType() == typeof(int);

Here we check if the run-time type of the value object is int by comparing it the result of the expression typeof(int).

Similar to the GetType() method which returns the Type of an object, the typeof() operator takes the name of a type as a parameter and returns an instance of System.Type.

Now, let’s exercise this code with some examples:

var boolResult1 = Methods.CheckIfIntegerWithEqualityOperator(123);
Console.WriteLine(boolResult1);

var boolResult2 = Methods.CheckIfIntegerWithEqualityOperator(456.0);
Console.WriteLine(boolResult2);

var boolResult3 = Methods.CheckIfIntegerWithEqualityOperator(0);
Console.WriteLine(boolResult3);

Running the examples yields the expected output:

True
False
True

Using Explicit Conversion to Check if an Object is a Number

As programmers, we have the option to use explicit conversion by specifying the conversion type and using a specific syntax.

Explicit Casting

We perform type casting when we assign a value of one data type to another. Explicit casting requires us to define the conversion explicitly using the casting operator:

public static bool Checkiffloatwithexplicitcast(object value)
{
    try
    {
        _ = (float)value;

        return true;
    }
    catch(InvalidCastException)
    {
        return false;
    }
}

In this method we begin by attempting to cast value to a float using an explicit cast. If it succeeds (does not throw an exception), we return true. 

It’s important to note that casting may fail at runtime, throwing an InvalidCastException exception. Let’s try it out:

var boolResult = Methods.CheckIfFloatWithExplicitCast(-2.5F);
Console.WriteLine(boolResult);

boolResult = Methods.CheckIfFloatWithExplicitCast(5L);
Console.WriteLine(boolResult);

boolResult = Methods.CheckIfFloatWithExplicitCast(5.0);
Console.WriteLine(boolResult);

Here we see the first result returns true, while the following two calls return false:

True
False
False

The last two methods return false because the explicit casting failed, throwing InvalidCastException.

These examples show that we use explicit casting to indicate the type of casting we want to achieve. However, it can lead to runtime exceptions when converting between incompatible numeric types and so we must code defensively.

Convert class methods

The Convert class offers static methods for explicit type conversions. To test it out, we will add CheckIfShortUsingConvert() to our Methods.cs file:

public static bool CheckIfShortUsingConvert(object value)
{
    try
    {
        _ = Convert.ToInt16(value);

        return true;
    }
    catch (OverflowException)
    {
        return false;
    }
    catch (FormatException)
    {
        return false;
    }
}

Once again we have a very simple method, we call Convert.ToInt16() with our passed value. If the conversion is successful we simply return true. If it fails due to an exception, we return false. Here we see that we need to be careful to handle both the OverflowExceptionand FormatException.

ToInt16() throws an OverflowException if the value argument is outside of the range of Int16.MinValue and Int16.MaxValue.

FormatException is thrown if the value argument is not a valid Int16 (short) format.  

Let’s exercise our method using various values of different data types:

var convertResult = Methods.CheckIfShortUsingConvert(5L);
Console.WriteLine(convertResult);

convertResult = Methods.CheckIfShortUsingConvert(19);
Console.WriteLine(convertResult);

convertResult = Methods.CheckIfShortUsingConvert(12.54F);
Console.WriteLine(convertResult);
                                                    
convertResult = Methods.CheckIfShortUsingConvert("255");
Console.WriteLine(convertResult);

convertResult = Methods.CheckIfShortUsingConvert(Decimal.MaxValue);
Console.WriteLine(convertResult);                                                   

convertResult = Methods.CheckIfShortUsingConvert("ABC");
Console.WriteLine(convertResult);

Now, let’s see the output:

True
True
True
True
False
False

First, notice that the last two conversions failed. The first one because Decimal.MaxValue is outside the range of short.MinValue and short.MaxValue, this throwing an OverflowException when attempting to convert it to a short. The second failure: "ABC", failed due to a FormatException, since ABC is not an appropriate short value.

Second, and something important to note is that even though all 4 of the first method calls passed in values that are not of type short, they all successfully converted to a short. In the context of our article, determining whether an object is a number or not, we need to understand that the Convertclass methods will succeed if the provided value is convertible to the desired number. For example we passed in the string “255” as an argument and this call succeeded because the string represented a valid short value.

Looking at these examples, we can conclude that when we call a conversion method, the result will depend on the variable type at run time and the target base type and will produce either:

  • a successful conversion,
  • no conversion (from a type to itself ),
  • an InvalidCastException exception (specific conversion between non-compatible types),
  • an OverflowException exception (narrowing conversion resulting in a loss of data),
  • a FormatException exception (converting a string value of an incorrect format to a numeric type).

Check if an Object Is a Number With Type Testing Operators

Type testing operators allow us to perform type checking or type conversion. The C# language provides us with two type-testing operators.

Firstly, the is operator is used to check if an expression’s run-time type matches a given type. Secondly, the as operator is used to try to convert an expression’s run-time type to a given type.

The Type comparison operator ”is”

The is operator tests if an expression matches a pattern, and returns a boolean. 

There are multiple patterns, but we won’t see them all here as it is out of the scope of this article. For more details, check out our article: “Pattern Matching in C#.

First, we are going to present the most common pattern, called Type pattern:

if(x is float) { ... }

Here we use the is keyword followed by a type name, to compare the type of the variable. The result of this expression is true if x is a float, else false.

In addition, we can utilize the Constant pattern to directly match a specific value:

if(value is 3.141592654) { ... }

This expression is equivalent to:

value is double && (double)value == 3.141592654

Finally, let’s discuss the programming pattern known as the Declaration pattern.  This pattern introduces a new local variable while using the is operator. Thus, if the pattern matches an expression, that variable receives the converted expression result and is available for consumption:

public static double CalculateAllTaxesIncludedPrice(object tax)
{
    double price = 28;
    if (tax is double vat)
    {
        price = price + (price * vat)/100;
    }
    return price;
}

When applying a tax of 10.0 (which is a double value by default), we obtain a price equal to 30.8:

var price = Methods.CalculateAllTaxesIncludedPrice(10.0);

The Type Conversion Operator ”as”

The as operator casts the result of an expression to a specified type. If the conversion fails, it returns null. It doesn’t throw any exception.

The as operator must be used with a reference type or a nullable value type:

public static bool CheckIfIntWithAsOperator(object value)
{
    var amount = value as int?;

    return amount is not null;
}

With the as operator, it is important to subsequently verify if the result is null or not, to avoid a NullReferenceException when attempting to use the value.

var intValue = Methods.CheckIfIntWithAsOperator(19);
Console.WriteLine(intValue);

intValue = Methods.CheckIfIntWithAsOperator(.25);
Console.WriteLine(intValue);

With these examples, we get the outputs listed below:

True
False

A Custom Extension Method

Now that we better understand the basics, we have sufficient knowledge to be able to write an extension method to check if an object is a number:

public static class Extensions
{
    public static bool IsNumber(this object value)
    {
        return value is sbyte
            || value is byte
            || value is short
            || value is ushort
            || value is int
            || value is uint
            || value is long
            || value is ulong
            || value is Int128
            || value is UInt128
            || value is nint
            || value is nuint
            || value is Half
            || value is float
            || value is double
            || value is decimal;
    }
}

Here, we have a simple boolean or statement that checks whether value is one of any of the numeric types returning true if it is and false otherwise.

As a side note, for those who prefer to use pattern-matching, this method could also be written in that style:

public static bool IsNumber(this object value)
    => value is sbyte or byte or short or ushort or ... or decimal;

Now, let’s see it in action:

object value = "123";
var isNumber = value.IsNumber();
Console.WriteLine(isNumber);

value = 123;
isNumber = value.IsNumber();
Console.WriteLine(isNumber);

Which yields the following expected results:

False
True

INumber<T> interface

With .Net 7 and C# 11, a new way of dealing with numbers appeared, by using the INumber<T> interface.

From now on, numbers implement INumber<T> interface, from the System.Numerics namespace.

We can now write generic methods on numbers, whatever the type T. Let’s see it in a small example:

public static bool IsNumber<T>(object obj) where T : INumber<T>
{
    return obj is INumber<T>;
}

It is crucial to mention the constraint on the type parameter T, which is required to implement the INumber<T> interface.

We can now call the method IsNumber() with all kinds of number types:

var response = Methods.IsNumber<double>(12.58);
Console.WriteLine(response);

response = Methods.IsNumber<long>(1299951437765);
Console.WriteLine(response);

As expected, we get True in response to both calls.

Conclusion

In this article, we have studied different ways to verify if an object is a number. Working with numbers is a common practice in programming and as a result, we must identify the type of data we are handling to prevent data loss or exceptions during processing. Thankfully, we now have the tools to solve the problem.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!