在C#中对任意对象进行数学运算

4

我正在使用C#实现一个玩具语言的解释器,为了在该语言中进行数学运算,我想要实现以下函数:

public static object Add( object a, object b )
{
  // return the sum of a and b
  // each might be int, double, or one of many other numeric types
}

我可以想象出一个非常愚蠢和糟糕的实现方式,它会根据a和b的类型(使用is运算符)分支成很多个,并且还需要进行许多强制类型转换,但我的直觉告诉我有更好的方法。
你认为这个函数的良好实现方式是什么?
5个回答

21

如果:

  • 你只想要一个易于编程的解决方案
  • 你的编程语言与C#有相同的算术规则
  • 你可以使用C# 4
  • 你不特别关心性能

那么你可以简单地这样做:

public static object Add(dynamic left, dynamic right)
{
    return left + right;
}

完成。这个方法被调用时,代码将会再次启动C#编译器并询问编译器:“如果你必须在编译时知道它们的运行时类型,你该如何添加这两个元素?”(动态语言运行时会缓存结果,这样下一次有人尝试添加两个整数时,编译器就不会再次启动,而是直接重用由编译器返回给DLR的Lambda表达式)。

如果您想实现自己的加法规则,那么欢迎来到我的世界。没有任何魔法可以避免大量的类型检查和转换。对于任意两种类型相加,可能存在数百种情况,您必须对它们进行全面检查。

在C#中处理这种复杂性的方式是我们在一个较小的类型子集上定义加法运算符:int、uint、long、ulong、decimal、double、float、所有枚举、所有委托、字符串和这些值类型的所有可空版本。 (然后将枚举视为其基础类型,这进一步简化了问题。)

例如,当您将ushort与short相加时,我们通过说ushort和short都是int的特殊情况来简化问题,然后解决添加两个int的问题。这大大减少了我们必须编写的代码量。但请相信我,C#中的二进制运算符重载解析算法有成千上万行代码。这不是一个简单的问题。

如果您的玩具语言旨在成为一种具有自己规则的动态语言,则可以考虑实现IDynamicMetaObjectProvider并使用DLR机制来实现算术和其他操作(如函数调用)。


Eric,非常感谢你这个周到而且信息量丰富的回答。当我提出问题时,绝对没有想到会得到这样高质量的回答!非常感谢。 - Harold
请查看我的评论,关于运算符重载的问题。在我看来,这是更好的选择,而且运算符重载就是为此而设计的。 - Mathias Lykkegaard Lorenzen

6

将您的值转换为最广泛的类型,例如,转换为十进制。所有类型(如int,double,short等)都实现了IConvertible接口 (http://msdn.microsoft.com/en-us/library/system.iconvertible_members.aspx)。它公开了ToDecimal方法,可用于将值转换为Decimal类型。同时,Convert类也非常有用。

decimal aop = Convert.ToDecimal(a);
decimal bop = Convert.ToDecimal(b);
decimal sum = aop + bop;
return Convert.ChangeType(sum, typeof(a)); // Changing type from decimal to type of the first operand.

这是关于“将所有内容转换为十进制数”的一个有趣的扩展/变体。感谢你分享这个。 - Harold

1

你可以做的一件事是为玩具语言编写自己的对象基类。它可以是一个真正的类或接口。这样,你就可以确保所有的类都有某种功能来处理所有操作(即使只是抛出运行时NotSupported异常)。你可以使用接口或抽象函数来处理常见的事情(如ToString或Equals),或者使用消息传递或其他方法来处理不常见的操作。

(附言:我和STO同时发布了帖子,但我喜欢STO关于数字类型的想法。)


1

最简单的方法就是像你说的那样测试类型。

但由于这是为玩具语言实现(祝贺你!)而设计的,我建议使用比object更好的抽象来传递解释器中的值。也许可以创建一个LanguageNameObject基类,然后添加所有帮助你实现此方法所需的辅助方法。基本上,Object类对你来说是一个糟糕的抽象....所以要构建一个更好的抽象!


0

很有趣,我会在输入的对象上使用typeof()运算符,并对那些可以执行Add操作的类型进行测试。我还想象你会抛出异常,如果奇怪的类型试图相加?

但是,如果您只允许添加int,float,double等,则最好创建重载版本的Add方法来处理这些不同情况。


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