C# 为什么不';t数字类型共享一个公共接口?
我最近遇到了一个问题,我想让一个函数同时处理double和integer,我想知道为什么没有适用于所有数字类型(包含算术运算符和比较)的通用接口 这将使编写像C# 为什么不';t数字类型共享一个公共接口?,c#,C#,我最近遇到了一个问题,我想让一个函数同时处理double和integer,我想知道为什么没有适用于所有数字类型(包含算术运算符和比较)的通用接口 这将使编写像Math.Min(以无数重载形式存在)这样的函数更加方便 引入一个额外的接口会是一个突破性的改变吗 编辑: 我想用这个 public T Add<T>(T a, T b) where T: INumber { return a+b; } public T Add(ta,tb),其中T:INumber { 返回a+b;
Math.Min
(以无数重载形式存在)这样的函数更加方便
引入一个额外的接口会是一个突破性的改变吗
编辑:
我想用这个
public T Add<T>(T a, T b) where T: INumber
{
return a+b;
}
public T Add(ta,tb),其中T:INumber
{
返回a+b;
}
或
public T Range(tx,tmin,tmax),其中T:INumber
{
返回最大值(x,最小值(x,最大值),最小值);
}
无论如何,您不能在接口中定义运算符,结构(尽管它们支持接口)在接口实现中无法很好地工作,因为这需要装箱和拆箱,当纯粹通过接口实现执行数学运算时,这当然会对性能造成很大影响
我还要强调,当您将结构强制转换到其接口时,结果对象是一个引用类型(装箱对象),您可以对其执行操作,而不是原始结构本身:
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
操作,但我的结构的原始实例不会更改。这是“强类型语言”的主要特征。这是一种每分钟可以避免数十亿次错误的方法。当然,我们希望int与double完全不同。即如何实现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;
}
}
公共静态类扩展编号
{
公共静态T范围(此T输入,T最小值,T最大值),其中T:class
{
返回input.Max(input.Min(Max),Min);
}
公共静态T Min(此T输入,参数T[]param),其中T:class
{
返回null;
}
私有静态T Max(此T输入,参数T[]编号),其中T:class
{
返回null;
}
}
我使用了
其中T:class
只是为了编译问题是,在数字存储的体系结构中,不同的数字类型基本上是不同的。首先,你的措辞是错误的,接口也不起作用,但我认为你的意思是你希望数字是松散类型的
<> P>只是一开始,为什么你不想这样做,考虑整数类型是一个一对一的映射在它们可以代表的值范围内,而浮点类型有一个PurSIS和指数分量,因为有很多浮点数。语言开发人员必须在语言设计中做出一些非常基本且可能导致错误的假设
查看关于浮点数学的更多信息。按照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
}
如果你想做这种“泛型”算术,你在C#等强类型语言中的选择是非常有限的。Marc Gravell: .NET2.0将泛型引入了.NET世界,为现有问题的许多优雅解决方案打开了大门。泛型约束可用于将类型参数限制为已知接口等,以确保对功能的访问,或对于简单的相等/不相等测试,分别使用
Comparer.Default
和EqualityComparer.Default
单例实现IComparer
和IEqualityComparer
(例如,允许我们对元素进行排序,而不必知道有关“T”的任何信息)
尽管如此,在运算符方面仍然存在很大的差距。由于运算符被声明为静态方法,因此没有所有数值类型实现的IMath
或类似的等效接口;事实上,运算符的灵活性将使这很难以有意义的方式实现。更糟糕的是,许多运算符基元类型上的运算符甚至不作为运算符存在,而是直接的IL方法。为了使情况更加复杂,Nullable需要“提升运算符”的概念,其中内部的“t”描述适用于Nullable类型的运算符,但这是作为语言功能实现的,并且不是由运行时提供的(让反思变得更加有趣)
但是,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);
}
}
您应该知道,这会删除类型安全性,如果动态运行时无法找到合适的重载进行调用(例如,因为您混合了类型),则在运行时可能会出现异常
如果要获得类型安全性,您可能需要查看库中提供的类,这些类为基本操作提供了泛型运算符
请注意,如果仅在特定操作之后,您实际上可能会使用内置类型已经实现的接口。例如,类型安全的通用Min
函数可能如下所示:
public static T Min<T>(params T[] values) where T : IComparable<T>
{
T min = values[0];
foreach (var item in values.Skip(1))
{
if (item.CompareTo(min) < 0)
min = item;
}
return min;
}
publicstatictmin(参数T[]值),其中T:IComparable
{
T min=数值[0];
foreach(values中的var项。跳过(1))
{
如果(项目比较(最小值)<0)
最小值=项目;
}
返回最小值;
}
这并不像引入一个接口那么简单,因为可用的运算符在每种类型上都不同,而且并不总是相同的(即DateTime+TimeSpan=>Da)
public static T Min<T>(params T[] values) where T : IComparable<T>
{
T min = values[0];
foreach (var item in values.Skip(1))
{
if (item.CompareTo(min) < 0)
min = item;
}
return min;
}
T x = ..., y = ...;
T sum = (dynamic)x + (dynamic)y;
T x = ..., y = ...;
T sum = Operator.Add(x, y); // actually Add<T>