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 OverflowException
and 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 Convert
class 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.