3

I'm new to C# and I want to create a function that accepts any numeric parameter, no matter if it's int/float, signed/unsigned, short/long etc and another function that accepts any other type.

In C++ this can be easily done using SFINAE and std::is_arithmetic:

template<typename T>
typename std::enable_if<std::is_arithmetic<T>::value>::type DoSomething(T)
{
    std::cout<<"int/float/char..."<<std::endl;
}

template<typename T>
typename std::enable_if<!std::is_arithmetic<T>::value>::type DoSomething(T)
{
    std::cout<<"any other type except int/float..."<<std::endl;
}

How may I achieve similar behavior in C#? I don't want to create an overload for every numeric type as the code for all numeric types as the code is exactly the same.

Mircea Ispas
  • 20,260
  • 32
  • 123
  • 211
  • 1
    I can't think of a (built-in) way to determine if a type in c# is "numeric". you can distinguish between _value_ and _reference_ types, but that's not the same. And you cannot use any operators like `+` or `*` for generic types. So I guess a solution for you depends on what your trying to achieve, or more precise, what makes a type "numeric" for you. – René Vogt Mar 29 '17 at 15:12
  • The only constraint I can think of to limit `T` would be forcing it to be `IComparable` as opposed to just `struct`. Not the best, but most limiting from what I can think of. – TyCobb Mar 29 '17 at 15:24
  • @Ðаn But there are use cases with an hypothetical `IArithmetic` constraint that doesn't necessarily constrain to "primitve" types. For example real and complex number matrixes. Code is pretty much identical but why pay the performance price of complex numbers when maybe you only need real numbers? – InBetween Mar 29 '17 at 15:24
  • 1
    If nothing else helps (see links provided in comments and answer, and comments in those links) - you can use T4 template to generate duplicated code for you. – Evk Mar 29 '17 at 15:25
  • @Ðаn well yes, but `Matrix`, `Matrix` and `Matrix` all seem reasonable, and there is no easy way to do this in C# avoiding duplicate code at the moment. Would someone also be able to create a `Matrix` or `Matrix`? Well yes, and it will work. Will it be useful? Well, who knows? Why not? – InBetween Mar 29 '17 at 15:27
  • 1
    do you accept the idea of using dynamics? http://stackoverflow.com/a/8122675/4537762 ;) – Anderson Rancan Mar 29 '17 at 15:38
  • @Ðаn I don't want to permit any user defined type. In C++ `is_arithmetic` is true only for builtin types (http://en.cppreference.com/w/cpp/types/is_arithmetic). Also the code was just an example. Conditions that groups types based on their properties into behaviors are quite common in practice and this grouping should be done to avoid code duplication. – Mircea Ispas Mar 29 '17 at 15:42

2 Answers2

1

You can't do this in C#. You are stuck with either defining all possible overloads or choosing the "bigger" type that can hold (precisely or not) all the expected values you would have to deal with.

This is an issue that crops up quite often. Read this or this for very knowledgeable information on the subject.

Alexander Oh
  • 24,223
  • 14
  • 73
  • 76
InBetween
  • 32,319
  • 3
  • 50
  • 90
1

I've been able to come up with something that is along the lines of what you're looking for; it's far less elegant than what can be done in C++.

The root of it is a struct that looks like

namespace TypeTraits
{
struct Number<T, TOperations> : IComparable, IFormattable, IConvertible, IComparable<Number<T, TOperations>>, IEquatable<Number<T, TOperations>>, IComparable<T>, IEquatable<T>
    where T : IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
    where TOperations : Operations<Number<T, TOperations>>, new()
{
    static readonly Operations<Number<T, TOperations>> Operations = new TOperations();

    public T Value { get; }
    public Number(T value)
    {
        Value = value;
    }
    public static implicit operator Number<T, TOperations>(T source) => new Number<T, TOperations>(source);
    public static implicit operator T(Number<T, TOperations> source) => source.Value;

    public override bool Equals(object obj) => Value.Equals(obj);
    public override int GetHashCode() => Value.GetHashCode();
    public override string ToString() => Value.ToString();

    public bool Equals(T other) => Value.Equals(other);
    public bool Equals(Number<T, TOperations> other) => Equals(other.Value);

    public int CompareTo(object obj) => Value.CompareTo(obj);
    public int CompareTo(T other) => Value.CompareTo(other);
    public int CompareTo(Number<T, TOperations> other) => CompareTo(other.Value);

    public string ToString(string format, IFormatProvider formatProvider) => Value.ToString(format, formatProvider);

    public TypeCode GetTypeCode() => Value.GetTypeCode();
    public bool ToBoolean(IFormatProvider provider) => Value.ToBoolean(provider);
    public byte ToByte(IFormatProvider provider) => Value.ToByte(provider);
    public char ToChar(IFormatProvider provider) => Value.ToChar(provider);
    public DateTime ToDateTime(IFormatProvider provider) => Value.ToDateTime(provider);
    public decimal ToDecimal(IFormatProvider provider) => Value.ToDecimal(provider);
    public double ToDouble(IFormatProvider provider) => Value.ToDouble(provider);
    public short ToInt16(IFormatProvider provider) => Value.ToInt16(provider);
    public int ToInt32(IFormatProvider provider) => Value.ToInt32(provider);
    public long ToInt64(IFormatProvider provider) => Value.ToInt64(provider);
    public sbyte ToSByte(IFormatProvider provider) => Value.ToSByte(provider);
    public float ToSingle(IFormatProvider provider) => Value.ToSingle(provider);
    public string ToString(IFormatProvider provider) => Value.ToString(provider);
    public object ToType(Type conversionType, IFormatProvider provider) => Value.ToType(conversionType, provider);
    public ushort ToUInt16(IFormatProvider provider) => Value.ToUInt16(provider);
    public uint ToUInt32(IFormatProvider provider) => Value.ToUInt32(provider);
    public ulong ToUInt64(IFormatProvider provider) => Value.ToUInt64(provider);

    public static Number<T, TOperations> operator+(Number<T, TOperations> lhs, Number<T, TOperations> rhs) => Operations.Add(lhs, rhs);
}
}

yes, that's a lot of code; but most of it is simple pass-through.

You then have an abstract Operations class and a couple of concrete implementations. This is the "trick" to get things into a type so it can be used as a generic parameter.

namespace TypeTraits
{
class Operations<T>
    where T : IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
{
    // names from https://msdn.microsoft.com/en-us/library/ms182355.aspx
    public virtual T Add(T a, T b)
    {
        throw new NotImplementedException();
    }
}

sealed class OperationsInt32 : Operations<Int32>
{
    public override Int32 Add(Int32 a, Int32 b)
    {
        return a.Value + b.Value;
    }
}

sealed class OperationsDouble : Operations<Double>
{
    public override Double Add(Double a, Double b)
    {
        return a.Value + b.Value;
    }
}
}

Finally, at the top-level (outside of namespace), some using aliases

using Int32 = TypeTraits.Number<System.Int32, TypeTraits.OperationsInt32>;
using Double = TypeTraits.Number<System.Double, TypeTraits.OperationsDouble>;

With all that in place, you can now write code like:

class Program
{
    static void DoSomething<T>(T t)
    {
        Console.WriteLine("DoSomething<T>");
    }

    static void DoSomething<T, TOperations>(Number<T, TOperations> t)
        where T : IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
        where TOperations : Operations<Number<T, TOperations>>, new()
    {
        Number<T, TOperations> t2 = t;
        var t3 = t + t2;
        Console.WriteLine("DoSomething<Number<T, TOperations>>");
    }


    static void Main(string[] args)
    {
        string s = "314";
        Int32 i = 314;
        Double d = 3.14;

        DoSomething(s);
        DoSomething(i);
        DoSomething(d);
    }
}

It might be possible to simplify this a bit by removing some constraints ...

Ðаn
  • 10,934
  • 11
  • 59
  • 95