为什么数字类型不共享通用接口?

我最近遇到了这个问题,我想要一个函数来处理双精度和整数,并且想知道为什么所有数字类型都没有通用接口(包含算术运算符和比较)。

它会使像Math.Min这样的Math.Min (存在于Math.Min重载中)更加方便。

引入一个额外的接口是一个突破性的变化吗?

编辑:我想考虑使用这个

 public T Add(T a, T b) where T: INumber { return a+b; } 

要么

 public T Range(T x, T min, T max) where T:INumber { return Max(x, Min(x, max), min); } 

如果你想做这种“通用”算术,你选择强类型语言如C#是非常有限的。 Marc Gravell 将问题描述如下 :

.NET 2.0在.NET世界中引入了generics,为现有问题的许多优雅解决方案打开了大门。 通用约束可用于将类型参数限制为已知接口等,以确保对function的访问 – 或者对于简单的相等/不等性测试, Comparer.DefaultEqualityComparer.Default单例实现IComparerIEqualityComparer分别允许我们对元素进行排序,而不必知道有关“T”的任何信息。

尽管如此,在运营商方面仍然存在很大差距。 因为运算符被声明为静态方法,所以没有所有数值类型实现的IMath或类似的等效接口; 事实上,运营商的灵活性会使这项工作变得非常困难。 更糟糕的是:原始类型的许多运算符甚至不作为运算符存在; 相反,有直接的IL方法。 为了使情况更加复杂,Nullable <>要求“提升运算符”的概念,其中内部“T”描述适用于可空类型的运算符 – 但这是作为语言特征实现的,并不是由运行时(使反射更有趣)。

但是,C#4.0引入了dynamic关键字,您可以使用该关键字在运行时选择正确的重载:

 using System; public class Program { static dynamic Min(dynamic a, dynamic b) { return Math.Min(a, b); } static void Main(string[] args) { int i = Min(3, 4); double d = Min(3.0, 4.0); } } 

您应该知道这会消除类型安全性,如果动态运行时无法找到合适的调用重载,例如因为您混合了类型,则可能会在运行时获得exception。

如果您想获得类型安全性,您可能需要查看MiscUtil库中可用的类,为基本操作提供通用运算符。

请注意,如果您仅在特定操作之后,实际上可能使用内置类型已实现的接口。 例如,类型安全的通用Min函数可能如下所示:

 public static T Min(params T[] values) where T : IComparable { T min = values[0]; foreach (var item in values.Skip(1)) { if (item.CompareTo(min) < 0) min = item; } return min; } 

即如何做2 + 2.35? 返回4或4.35还是4.349999? 界面如何理解什么是适当的输出? 您可以编写扩展方法并使用重载来解决此问题,但是如果我们想要为所有目的设置接口接口大小多长时间并且难以找到有用的function,那么接口也增加了一些开销和数字通常是基础计算所以需要快速的方式。

我认为在你的情况下写一个扩展类更好:

 public static class ExtensionNumbers { public static T Range(this T input, T min, T max) where T : class { return input.Max(input.Min(max), min); } public static T Min(this T input, params T[] param) where T : class { return null; } private static T Max(this T input, params T[] number) where T : class { return null; } } 

我用where T : class来编译

好吧,你无法在接口中定义运算符和结构(尽管它们支持接口)在接口实现时不能很好地工作,因为这需要装箱和拆箱,当然这在纯粹执行数学运算时会受到很大的打击。通过接口实现。

我还要强调,当你将一个结构体转换为它的接口时,结果对象是你执行操作的引用类型(一个盒装对象),而不是原始结构本身:

 interface IDoSomething { void DoSomething(); } struct MyStruct : IDoSomething { public MyStruct(int age) { this.Age = age; } public int Age; pubblic void DoSomething() { Age++; } } public void DoSomething(IDoSomething something) { something.DoSomething(); } 

当我传入我的结构实例时,它的盒装(成为引用类型),我执行我的DoSomething操作,但我的结构的原始实例不会改变。

根据马修的回答,请注意3次调用之间的区别。

 void DoSomething(ref MyStruct something) { something.DoSomething(); } static void Main(string[] args) { var s = new MyStruct(10); var i = (IDoSomething)s; DoSomething(s); // will not modify s DoSomething(i); // will modify i DoSomething(ref s); // will modify s, but with no reassignment } 

它并不像引入接口那么简单,因为可用的运算符每种类型都不同,并且并不总是均匀的(即DateTime + TimeSpan => DateTime,DateTime – DateTime => TimeSpan)。

在技​​术水平,这里可能涉及很多拳击等,而常规操作员是static

实际上,对于Min / MaxComparer.Default.Compare(x,y)几乎可以完成您所希望的一切。

对于其他运营商:在.NET 4.0中, dynamic在这里有很大的帮助:

 T x = ..., y = ...; T sum = (dynamic)x + (dynamic)y; 

但是否则“ MiscUtil ”通过Operator类对运算符有通用支持 ,即

 T x = ..., y = ...; T sum = Operator.Add(x, y); // actually Add 

问题在于,在如何存储数字的体系结构中,不同数字类型的处理方式不同。 对于初学者来说,你的措辞是错误的并且接口不起作用,但我认为你得到的是你想要数字的松散类型。

只是为了开始为什么你不想这样做,考虑整数类型是他们可以表示的值范围的一对一映射,而浮点类型有一个persision和exponent组件,因为有很多浮动点数字。 语言开发人员必须在语言设计中做出一些非常基本且可能导致错误的假设。

有关更多信息,请查看有关浮点数学的这篇文章 。

这是“强类型语言”的主要特征。 这可以避免一分钟数十亿的错误。 当然我们希望int与double完全不同。