我想要分享一个新模式,我开发来用于在 C# 中利用运行时编译进行泛型计算。
过去的几年里我已经在编程并且看到许多在 C# 中实现泛型数学的例子,但是没有一个能做得非常地好。在第一部分中我将一步步地解说我看到的一些例子,同时也会说明为什么他们没有向泛函提供好的模式。
我开发了这个模式以作为我的 Seven Framework 框架工程的一部分。如果你感兴趣的话你可以点击:https://github.com/53V3N1X/SevenFramework。
问题概述
首先,根本的问题是在 C# 中处理泛型就像基类一样。除了 System.Object 基类,他们都没有隐式成员。也就是说,相对于标准数值类型(int,double,decimal,等)他们没有算数运算符和隐式转换。EXAMPLE 1 是一种理想情况,但是这段代码在标准 C# 中是不能编译的。
// EXAMPLE 1 ----------------------------------------
namespace ConsoleApplication
{ public class Program
{ public static void Main(string[] args)
{ Sum<int>(new int[] { 1, 2, 3, 4, 5 }); }
public static T Sum<T>(T[] array)
{ T sum = 0; // (1) cannot convert int to generic
for (int i = 0; i < array.Length; i++)
sum += array[i]; // (2) cannot assume addition operator on generic
return sum; }
}
}
确实如此,EXAMPLE 1 不能通过编译因为:
类型"T"不能确保int数值的隐式转换。
类型"T"不能确保一个加法操作。
现在我们了解了根本问题后,让我们开始寻找一些方法克服它。
接口化解决方法
C#中的 where 子句是一种强迫泛型满足某种类型的约束。然而,用这种方法就要求有一种不存在于C#中的基本数值类型。C#有这样一种基本数值类型是最接近强制泛型成为数值类型的可能了,但这并不能在数学上帮助我们。EXAMPLE 2 仍然不能够通过编译,但如果我们创造出了我们自己的基本数值类型,这将成为可能。
Hide Copy Code
// EXAMPLE 2 ----------------------------------------
namespace ConsoleApplication {
public class Program
{
public static void Main(string[] args)
{
Sum<int>(new int[] { 1, 2, 3, 4, 5 });
}
public static T Sum<T>(T[] array)
where T : number // (1) there is no base "number" type in C#
{ T sum = 0;
for (int i = 0; i < array.Length; i++)
sum += array[i];
return sum; }
}
}
现在 EXAMPLE 2 还不能编译因为:
在C#中没有基本“数值”类型。
如果我们实现了我们自己的基本“数值”类型,就可以让它通过编译。我们所必需做的就是迫使这个数值类型拥有C#基本数值类型一般的算数运算符和隐式转换。逻辑上来讲,这应该是一个接口。
然而,即使我们自己做数值接口,我们仍然有一个重大问题。我们将不能够对 C# 中的基本类型做通用数学计算,因为我们不能改变 int,double,decimal 等的源代码来实现我们的接口。所以,我们不仅必须编写自己的基本接口,还需要为C#中的原始类型编写包装器。
在例3中,我们有我们自己的数值接口,“数字”,和原始类型int的包装器,Integer32。
// EXAMPLE 3 ----------------------------------------
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Sum(new Number[]
{
new Integer32(1), // (1) initialization nightmares...
new Integer32(2),
new Integer32(3),
new Integer32(4),
new Integer32(5)
});
}
public static Number Sum(Number[] array)
{
Number sum = array[0].GetZero(); // (2) instance-based factory methods are terrible design
for (int i = 0; i < array.Length; i++)
sum = sum.Add(array[i]);
return sum;
}
}
public interface Number
{
Number GetZero(); // (2) again... instance based factory methods are awful
Number Add(Number other);
}
public struct Integer32 : Number // (3) C# primitives cannot implement "Number"
{
int _value;
public Integer32(int value)
{
this._value = value;
}
Number Number.GetZero()
{
return new Integer32(0);
} // (4) you will have to re-write these functions for every single type
Number Number.Add(Number other)
{
return new Integer32(_value + ((Integer32)other)._value);
}
}
} // (5) this code is incredibly slow
好的,这样 EXAMPLE 3 就编译了,但是它有点糟,为什么呢:
编程时用接口初始化变量是非常丑陋的。
你不该用工厂方法或构造函数作为一个实例化方法,因为它是一种糟糕的设计并且很容易在程序各处造成空引用异常。
你不能让C#基本类型去实现“Number”接口所以只能使用自定义类型工作。
它不是泛函因为你必须每一步都写一个自定义的实现。
这段代码因封装了基本类型工作极其慢。