C# 中利用运行时编译实现泛函

我想要分享一个新模式,我开发来用于在 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”接口所以只能使用自定义类型工作。

它不是泛函因为你必须每一步都写一个自定义的实现。

这段代码因封装了基本类型工作极其慢。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/44fd2e25177d4cf95d97965a5212c7fd.html