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

时间:2010-12-01 08:18:28

标签: c#

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

这样可以使编写像Math.Min这样的函数(存在于无数过载中)更方便。

引入额外的界面是否会发生重大变化?

修改 我想用这个就像

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

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

7 个答案:

答案 0 :(得分:8)

如果你想做这种“通用”算术,你选择C#等强类型语言是非常有限的。 Marc Gravell described the problem as follows

  

.NET 2.0将泛型引入.NET世界,为现有问题的许多优雅解决方案打开了大门。通用约束可用于将类型参数限制为已知接口等,以确保对功能的访问 - 或者对于简单的相等/不等式测试,Comparer<T>.DefaultEqualityComparer<T>.Default单例实现IComparer<T>和{分别为{1}}(允许我们对元素进行排序,而不必知道有关“T”的任何内容)。

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

但是,C#4.0引入了IMath<T>关键字,可用于在运行时选择正确的重载:

dynamic

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

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

请注意,如果您只是在特定操作之后,您实际上可能会使用内置类型已经实现的接口。例如,类型安全的通用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); } } 函数可能如下所示:

Min

答案 1 :(得分:4)

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

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

public static class ExtensionNumbers
{
    public static T Range<T>(this T input, T min, T max) where T : class 
    {
        return input.Max(input.Min(max), min);
    }

    public static T Min<T>(this T input, params T[] param) where T : class
    {
        return null;
    }

    private static T Max<T>(this T input, params T[] number) where T : class 
    {
        return null;
    }      

}

我使用where T : class只是为了编译

答案 2 :(得分:1)

好吧,你无论如何都无法在接口中定义运算符,并且结构(尽管它们支持接口)不能通过接口实现很好地工作,因为这需要装箱和拆箱,这当然在执行时会有很大的性能影响。纯粹通过接口实现进行数学运算。

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

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();
} 

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

答案 3 :(得分:1)

根据Matthew的回答,请注意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

}

答案 4 :(得分:1)

它并不像引入接口那么简单,因为可用的运算符因类型而异,并且并不总是均匀的(即DateTime + TimeSpan =&gt; DateTime,DateTime - DateTime =&gt; TimeSpan)。

在技术级别,这里可能涉及很多拳击等,而常规运算符是static

实际上,对于Min / MaxComparer<T>.Default.Compare(x,y)可以完成您希望的所有内容。

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

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

但是“MiscUtil”通过Operatorgeneric support for operators有{{3}},但

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

答案 5 :(得分:0)

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

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

有关更多信息,请查看有关浮点数学的article

答案 6 :(得分:-4)

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