【问题标题】:C# generics - overload based on type traits [duplicate]C#泛型 - 基于类型特征的重载
【发布时间】:2017-03-29 15:06:29
【问题描述】:

我是 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# 中实现类似的行为?我不想为每个数字类型创建一个重载,因为所有数字类型的代码都是一样的。

【问题讨论】:

  • 我想不出一种(内置)方法来确定 c# 中的类型是否为“数字”。您可以区分 valuereference 类型,但这不一样。并且您不能对泛型类型使用任何运算符,例如 +*。所以我想你的解决方案取决于你想要达到的目标,或者更准确地说,是什么让类型对你来说是“数字”的。
  • 我能想到的限制T 的唯一约束是强制它为IComparable,而不仅仅是struct。不是最好的,但在我能想到的范围内是最有限的。
  • @Ðаn 但是有些用例带有假设的IArithmetic&lt;T&gt; 约束,不一定限制为“原始”类型。例如实数和复数矩阵。代码几乎相同,但当您可能只需要实数时,为什么要为复数的性能付出代价?
  • 如果没有其他帮助(请参阅 cmets 和 answer 中提供的链接,以及这些链接中的 cmets) - 您可以使用 T4 模板为您生成重复的代码。
  • 你接受使用动态的想法吗? stackoverflow.com/a/8122675/4537762 ;)

标签: c# c++ templates generics


【解决方案1】:

您不能在 C# 中执行此操作。您要么定义所有可能的重载,要么选择可以容纳(准确或不准确)您必须处理的所有预期值的“更大”类型。

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

【讨论】:

    【解决方案2】:

    我已经能够想出一些与您正在寻找的东西相符的东西;它远不如 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);
        }
    }
    

    也许可以通过移除一些限制来简化这一点......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-01-12
      • 2021-07-02
      • 1970-01-01
      • 2019-06-04
      • 1970-01-01
      • 1970-01-01
      • 2018-02-11
      相关资源
      最近更新 更多