In this article, we will explore different methods to check if an object is a number.
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.
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 Type | Range | C# keyword |
|---|---|---|
| System.SByte | -128 to 127 | sbyte |
| System.Byte | 0 to 255 | byte |
| System.Int16 | -32,768 to 32,767 | short |
| System.UInt16 | 0 to 65,535 | ushort |
| System.Int32 | -2,147,483,648 to 2,147,483,647 | int |
| System.UInt32 | 0 to 4,294,967,295 | uint |
| System.Int64 | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | long |
| System.UInt64 | 0 to 18,446,744,073,709,551,615 | ulong |
| System.Int128 | -170141183460469231731687303715884105728 to 170141183460469231731687303715884105727 | |
| System.UInt128 | 0 to 340282366920938463463374607431768211455 | |
| System.IntPtr | Depends on platform (computed at runtime) | nint |
| System.UIntPtr | Depends on platform (computed at runtime) | nuint |
Now, let’s continue with the Floating-point numeric types:
| .Net Type | Approximate range | C# 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
InvalidCastExceptionexception (specific conversion between non-compatible types), - an
OverflowExceptionexception (narrowing conversion resulting in a loss of data), - a
FormatExceptionexception (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.

