C#泛型 - 基于类型特征的重载

时间:2017-03-29 15:06:30

标签: c# c++ templates generics

我是C#的新手,我想创建一个接受任何数字参数的函数,无论它是int / float,signed / unsigned,short / long等,还有另一个接受任何其他类型的函数。

在C ++中,可以使用SFINAE和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;
}

我如何在C#中实现类似的行为?我不想为每个数字类型创建一个重载作为所有数字类型的代码,因为代码完全相同。

2 个答案:

答案 0 :(得分:1)

你不能在C#中这样做。你很难定义所有可能的olverloads或者选择“更大”的类型,它可以保存(精确或不精确)你必须处理的所有预期值。

这是一个经常出现的问题。阅读thisthis,了解有关该主题的非常知识渊博的信息。

答案 1 :(得分:1)

我已经能够提出一些与你所寻找的东西相符的东西;它远不如C ++中那么优雅。

它的根是struct,看起来像

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);
}
}
是的,这是很多代码;但大部分都是简单的传递。

然后你有一个抽象的Operations类和几个具体的实现。这是将事物变成类型的“技巧”,因此它可以用作通用参数。

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;
    }
}
}

最后,在顶层(namespace之外),一些using别名

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

完成所有这些后,您现在可以编写如下代码:

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);
    }
}

可以通过删除一些约束来简化这一点......