泛型 - T 是否可以是数字?

74

我正在尝试找到一种方法来创建一个只能处理数字类型的通用类,以进行一些计算。

是否存在适用于所有数字类型(int、double、float等)的常见接口?

如果没有,那么创建这样一个类的最佳方法是什么?

更新:

我想要实现的主要目标是检查两个T类型变量之间的大小关系。


1
据我所知,这是不可能的。您可以在此处找到一些原因(以及替代解决方案)。 - Fredrik Mörk
1
请查看此帖子:https://dev59.com/gXVD5IYBdhLWcg3wRpeX - David
1
它们在这种情况下不起作用,但当你使用泛型时,了解“泛型类型约束”是很有好处的。http://msdn.microsoft.com/en-us/library/d5x73970%28VS.80%29.aspx - STW
2
你尝试过使用IConvertible / IComparable吗?-- 所有数字类都支持它。 - BrainSlugs83
8个回答

36

你正在使用哪个版本的.NET?如果你正在使用.NET 3.5,那么我有一个在MiscUtil(免费等)中的通用运算符实现

这个实现包括像T Add<T>(T x, T y)这样的方法,以及其他针对不同类型的算术变体(比如DateTime + TimeSpan)。

此外,它适用于所有内置、提升和定制运算符,并缓存委托以提高性能。

关于为什么这很棘手的一些背景信息可以在这里找到。

你可能还想知道,dynamic(4.0)也间接地解决了这个问题 - 也就是说,

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect

关于 < / > 的评论 - 实际上你不需要运算符;你只需要:
T x = ..., T y = ...
int c = Comparer<T>.Default.Compare(x,y);
if(c < 0) {
    // x < y
} else if (c > 0) { 
    // x > y
}

1
我正在使用.NET 3.5,我尝试执行“if(x > y)”操作,其中x和y的类型为T。 - CD..
1
MiscUtil有Operator.GreaterThan,但你甚至不需要它;将更新以展示为什么不需要... - Marc Gravell
1
MiscUtil的链接现在已经失效了。这是我能找到的最佳替代品:https://github.com/loory/MiscUtil - intrepidis
@intrepidis 我会更新链接(有比那更好的来源)。 - Marc Gravell

18

对于某些数字类型的操作,例如IComparable<T>IConvertibleIEquatable<T>接口,都有相应的接口。您可以指定特定的功能:

public class MaxFinder<T> where T : IComparable<T> {

   public T FindMax(IEnumerable<T> items) {
      T result = default(T);
      bool first = true;
      foreach (T item in items) {
         if (first) {
            result = item;
            first = false;
         } else {
            if (item.CompareTo(result) > 0) {
               result = item;
            }
         }
      }
      return result;
   }

}

您可以使用委托来扩展具有特定类型操作的类:
public class Adder<T> {

   public delegate T AddDelegate(T item1, T item2);

   public T AddAll(IEnumerable<T> items, AddDelegate add) {
      T result = default(T);
      foreach (T item in items) {
         result = add(result, item);
      }
      return result;
   }

}

使用方法:

Adder<int> adder = new Adder<int>();
int[] list = { 1, 2, 3 };
int sum = adder.AddAll(list, delegate(int x, int y) { return x + y; });

您还可以在类中存储委托,并拥有不同的工厂方法,为特定的数据类型设置委托。这样,特定类型代码仅存在于工厂方法中。


12

很抱歉,最接近你所需的是结构体。你需要在代码中进行更广泛的数字类型检查。

public class MyClass<T> where T : struct
(...)

11
为什么不使用“where T: struct, IConvertible”呢,这至少更接近要求。 - BrainSlugs83

10

由于您必须使用单个接口进行算术运算,因此您无法这样做。已经有很多在Connect上的请求添加一个名为“IArithmetic”接口以实现这一特定目的,但到目前为止它们都被拒绝了。

您可以通过定义一个没有成员的结构体来绕过这个问题,并实现一个“计算器”接口。我们在Pluto Toolkit的插值泛型类中采用了这种方法。例如,我们有一个“向量”计算器实现这里,它让我们的泛型插值器可以处理向量。还有类似的浮点数、双精度数、四元数等。


3
这是一个失效的链接! - happygilmore

7
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GenericPratice1
{
    public delegate T Del<T>(T numone, T numtwo)where T:struct;
    class Class1
    {
        public T Addition<T>(T numone, T numtwo) where T:struct
        {
            return ((dynamic)numone + (dynamic)numtwo);
        }
        public T Substraction<T>(T numone, T numtwo) where T : struct
        {
            return ((dynamic)numone - (dynamic)numtwo);
        }
        public T Division<T>(T numone, T numtwo) where T : struct
        {
            return ((dynamic)numone / (dynamic)numtwo);
        }
        public T Multiplication<T>(T numone, T numtwo) where T : struct
        {
            return ((dynamic)numone * (dynamic)numtwo);
        }

        public Del<T> GetMethodInt<T>(int ch)  where T:struct
        {
            Console.WriteLine("Enter the NumberOne::");
            T numone =(T) Convert.ChangeType((object)(Console.ReadLine()), typeof(T));
            Console.WriteLine("Enter the NumberTwo::");
            T numtwo = (T)Convert.ChangeType((object)(Console.ReadLine()), typeof(T));
            T result = default(T);
            Class1 c = this;
            Del<T> deleg = null;
            switch (ch)
            {
                case 1:
                    deleg = c.Addition<T>;
                    result = deleg.Invoke(numone, numtwo);
                    break;
                case 2: deleg = c.Substraction<T>;
                    result = deleg.Invoke(numone, numtwo);
                    break;
                case 3: deleg = c.Division<T>;
                    result = deleg.Invoke(numone, numtwo);
                    break;
                case 4: deleg = c.Multiplication<T>;
                    result = deleg.Invoke(numone, numtwo);
                    break;
                default:
                    Console.WriteLine("Invalid entry");
                    break;
            }
            Console.WriteLine("Result is:: " + result);
            return deleg;
        }

    }
    class Calculator
    {
        public static void Main(string[] args)
        {
            Class1 cs = new Class1();
            Console.WriteLine("Enter the DataType choice:");
            Console.WriteLine("1 : Int\n2 : Float");
            int sel = Convert.ToInt32(Console.ReadLine());
            Console.WriteLine("Enter the choice::");
            Console.WriteLine("1 : Addition\n2 : Substraction\3 : Division\4 : Multiplication");
            int ch = Convert.ToInt32(Console.ReadLine());
            if (sel == 1)
            {
                cs.GetMethodInt<int>(ch);
            }
            else
            {
                cs.GetMethodInt<float>(ch);
            }

        }
    }
}

6
在框架BCL(基类库)中,许多数字函数(如System.Math中的函数)通过为每种数字类型提供重载来处理此问题。
BCL中的静态Math类包含静态方法,您可以在不创建类实例的情况下调用。您也可以在自己的类中这样做。例如,Math.Max有11个重载版本:
public static byte Max(byte val1, byte val2);
public static decimal Max(decimal val1, decimal val2);
public static double Max(double val1, double val2);
public static short Max(short val1, short val2);
public static int Max(int val1, int val2);
public static long Max(long val1, long val2);
public static sbyte Max(sbyte val1, sbyte val2);
public static float Max(float val1, float val2);
public static ushort Max(ushort val1, ushort val2);
public static uint Max(uint val1, uint val2);
public static ulong Max(ulong val1, ulong val2);

3
你不能只在编译时做到这一点。 但是,您可以对数值类型施加更多限制来筛选出大部分“不良类型”,如下所示。
class yourclass <T>where T: IComparable, IFormattable, IConvertible, IComparabe<T>, IEquatable<T>, struct {... 

最后,您仍然需要在运行时使用object.GetType()方法检查您的类型是否可接受。

如果只是进行比较,则仅使用IComparable<T>即可完成。


3

我不相信你可以使用通用类型约束来定义它。你的代码可以在内部检查你的要求,可能使用Double.Parse或Double.TryParse来确定它是否是一个数字 - 如果VB.NET不是问题,那么你可以使用IsNumeric()函数。

编辑: 你可以添加对Microsoft.VisualBasic.dll的引用,并从C#中调用IsNumeric()函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接