是否有一个适用于“实数”类型的C#通用约束?

24

可能重复问题:
仅适用于整数的C#泛型约束

您好!

我正在尝试在C#中设置笛卡尔坐标系,但我不想将坐标值限制为任何一种数字类型。有时它们可以是整数,而其他时候它们可以是有理数,具体取决于上下文。

这让我想到了“泛型类”,但我对如何限制类型同时包含整数和浮点数感到困惑。我似乎找不到涵盖任何实数概念的类......

public class Point<T> where T : [SomeClassThatIncludesBothIntsandFloats?]  {
    T myX, myY;

    public Point(T x, T y) {
        myX = x;
        myY = y;
    }
}

Point<int> pInt = new Point<int>(5, -10);
Point<float> pFloat = new Point<float>(3.14159, -0.2357);

如果我想要这种自由度,那么在类内部进行计算时,是否会遇到“typeof(T)”噩梦,需要清除bools、strings、objects等?或者更糟糕的是,我是否需要为每种类型的数字都创建一个类,并且每个类都具有相同的内部数学公式?如有帮助将不胜感激。谢谢!

12
这是一个相当频繁被要求的功能。我们正在考虑将其作为编译器/运行时的假设未来版本的一个可能性,但它并不是最重要的任务之一,所以不能将其视为任何承诺。请记住,我们有数百个可能的功能,并且每个版本只能实现其中的一小部分。但我们肯定会关注此功能。 - Eric Lippert
1
这很好知道,Eric。我对C#非常陌生,但我有Java的基础,所以它很像跳跃维度......相似,但不同之处足以让你偶尔感到困惑。 ;) - Syndog
3
谢谢大家的回复。你们太棒了!难怪我每次在Google搜索C#问题时,stackoverflow.com总是排名很靠前。 - Syndog
2
这并不是一个绝对可靠的解决方案,但它将大大缩小类型参数的范围:public class Point<T> where T : IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>, struct {... - dmihailescu
我在这里添加了一些代码(http://codereview.stackexchange.com/questions/26022/improvement-requested-for-generic-calculator-and-generic-number),可能会对你有所帮助。 - Martin Mulder
8个回答

16
你无法定义这样的约束,但你可以在运行时检查类型。不过这对于进行计算是没有帮助的。
如果你想要进行计算,像这样做就可以了:
class Calculations<T, S> where S: Calculator<T>, new()
{
    Calculator<T> _calculator = new S();

    public T Square(T a)
    {
        return _calculator.Multiply(a, a);
    }

}

abstract class Calculator<T>
{
    public abstract T Multiply(T a, T b);
}

class IntCalculator : Calculator<int>
{
    public override int Multiply(int a, int b)
    {
        return a * b;
    }
}

同样地,定义一个FloatCalculator和你需要的任何操作。虽然不是特别快,但比C# 4.0的dynamic构造要快。
var calc = new Calculations<int, IntCalculator>();
var result = calc.Square(10);

一个副作用是,你只能实例化Calculator,如果你传递给它的类型有匹配的Calculator<T>实现,这样你就不必进行运行时类型检查了。
这基本上就是Hejlsberg在这次采访中所谈到的问题。个人而言,我仍然希望看到某种基础类型 :)

1
没错,这很有道理。基本上,我要将我的“原始”类型封装在实现了共同接口的类中,然后根据这个进行限制。谢谢,Thor! - Syndog
请注意,在.NET 3.5中,您仍然可以使用泛型来完成某些操作-有关详细信息,请参阅我的答案。 - Marc Gravell

13
这是一个非常普遍的问题;如果你正在使用.NET 3.5,那么在MiscUtil中有很多支持,通过Operator类,它支持内置类型和任何带有运算符的自定义类型(包括“提升”的运算符);特别是,这允许与泛型一起使用,例如:
public static T Sum<T>(this IEnumerable<T> source) {
    T sum = Operator<T>.Zero;
    foreach (T value in source) {
        if (value != null) {
            sum = Operator.Add(sum, value);
        }
    }
    return sum;
}

或者举个例子;Complex<T>

有趣。看起来它使用了LINQ表达式树功能,但是立即评估每一个位? - Thorarin
在内部,它使用表达式树来预编译(每个类型仅一次)一个委托来执行算术运算。 - Marc Gravell

7

嗨,Stan,只是想让你知道这很有帮助。通过一次性将T继承自结构体,就消除了所有值类型的运行时检查的需要。谢谢,伙计! - Syndog
3
您提供的链接显示“页面未找到”。 - weberc2

3
你实际上可以做到这一点,尽管解决方案很繁琐并且可能会让不了解其原因的开发人员感到困惑(所以如果你选择这样做,请仔细记录!)...
创建两个结构体,分别称为MyInt和MyDecimal,它们作为CTS Int32和Decimal核心类型的外观(它们包含该相应类型的内部字段)。每个结构体都应该有一个构造函数,该构造函数将Core CTS类型的实例作为输入参数。
使每个结构体实现一个名为INumeric的空接口。
然后,在您的通用方法中,根据此接口进行约束。缺点是,您想要使用这些方法的任何地方都必须构造适当的自定义类型的实例,而不是Core CTS类型,并将自定义类型传递给方法。
注意:编写自定义结构体以正确模拟所有核心CTS类型的行为是繁琐的部分...您必须实现几个内置CLR接口(IComparable等),并重载所有算术和布尔运算符...

1

通过实施更多的措施,您可以更接近目标。

public class Point<T> where T : struct, IComparable, IFormattable, IConvertible, 
                                IComparable<T>, IEquatable<T> {   
}

签名也符合 DateTime。我不确定您是否能够从框架中指定更多类型。无论如何,这只解决了问题的一部分。要进行基本的数字操作,您将不得不包装您的数字类型并使用通用方法而不是标准运算符。请参阅此 SO 问题以获取一些选项。

0

可能会有所帮助。您需要使用一个泛型类来实现您想要的功能。


0

0

这样不是会更适合采用实现 IPoint 接口的单独类吗?

比如:

public interface IPoint<T>  
{
    T X { get; set; }
    T Y { get; set; }
}

public class IntegerPoint : IPoint<int>
{

    public int X { get; set; }
    public int Y { get; set; }
}

因为计算在每个实现中都必须不同,对吧?

Dan#


2
好的,实际上这些计算是相同的,无论它们是使用整数还是浮点数值实现的。否则,我会只创建两个分别实现它们各自类型的类,而不使用任何泛型或继承。但是由于公式是相同的,我不想在类之间重复代码。感谢您的回复! - Syndog

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